3

如果我在 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;
}
4

2 回答 2

4

虽然我测试了你的代码示例,并且它使用 g++/gcc 和 clang++/clang 为我编译得很好,但是当你想编译基于 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您在代码中使用的内容,即使仅在标题中定义内联函数不是强制性的,但强烈建议您这样做,因此您不必将内联正文复制到每个翻译由于一个定义规则,您将在其中包含该标题的单元。

正如维基百科所说的那样

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

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

高温高压

于 2013-06-22T20:09:15.177 回答
0

这里实际上有两个问题:

  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 中正确格式化预处理器指令吗?

于 2013-06-22T20:20:59.017 回答