C在C++中超出标头

C out of header in C++

本文关键字:中超 C++      更新时间:2023-10-16

如果我在C++环境中使用C代码,并且我将所有代码都包含在头中,那么一切都很好。如果我试图在头中声明C函数,并且它们在.C或.cpp文件中实现它们,我会得到以下错误:

Undefined symbols for architecture x86_64:
  "vec2_norm(Vec2)", referenced from:
      _main in main.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Vec2.h

#ifndef Physics_Engine_Test_Vec2_h
#define Physics_Engine_Test_Vec2_h
typedef struct
{
    float x;
    float y;
} Vec2;
inline Vec2 vec2_norm(Vec2 v);
#endif

Vec2.c或.cpp

#include "Vec2.h"
#include <math.h>
inline Vec2 vec2_norm(Vec2 v) {
    float len = v.x*v.x + v.y*v.y;
    if (len) {
        len = 1 / sqrtf(len);
        v.x *= len;
        v.y *= len;
    }
    return v;
}

尽管我测试了您的代码示例,并且它使用g++/gcc和clang++/crang对我来说编译得很好,但当您想要编译基于C的源代码时,最好在它周围添加extern "C" {},这样编译器就不会对这些函数进行C++名称篡改:

#ifdef __cplusplus
extern "C" {
#endif
typedef struct
{
    float x;
    float y;
} Vec2;
inline Vec2 vec2_norm(Vec2 v);
#ifdef __cplusplus
};
#endif

extern "C" {
inline Vec2 vec2_norm(Vec2 v) {
    float len = v.x*v.x + v.y*v.y;
    if (len) {
        len = 1 / sqrtf(len);
        v.x *= len;
        v.y *= len;
    }
    return v;
}
};

顺便说一句,关于您在代码中使用的inline,尽管仅在标头中定义内联函数不是强制性的,但强烈建议您这样做,这样您就不必将内联正文复制到每个包含该标头的翻译单元,因为一个定义规则

正如维基百科在这个话题上所说:

有些东西,如类型、模板和外部内联函数,可以在多个翻译单元中定义。对于给定的实体,每个定义必须相同。不同翻译单元中的非外部对象和函数是不同的实体,即使它们的名称和类型相同。

但最终,这是一个好主意还是坏主意取决于你的设计选择。

HTH

这里实际上有两个错误:

  1. 如果在内联中声明一个函数,则除非在给定的编译单元中需要,否则不会对其进行编译。所以Vec2.o文件不会包含它,链接器也找不到它。内联函数总是必须放在头中,所以编译器在每个需要它的编译单元中都可以看到它们的实现。

  2. 正如H2CO3所说,C++使用名称篡改:它将参数类型编码到函数名称中,以允许重载。C不这么做。因此,如果您编译一个使用您的函数的c++文件,它将希望链接到某个奇怪的名称,而不仅仅是"vec2_norm"。为了能够将C代码链接到C++代码中,您必须告诉编译器它应该使用C符号名称。

大多数情况下,人们通过编写以下形式的标题来做到这一点:

#ifdef __cplusplus
    extern "C" {
#endif
Vec2 vec2_norm(Vec2 v);
#ifdef __cplusplus
    }
#endif

顺便问一下,有人知道如何在stackoverflow中正确格式化预处理器指令吗?