【问题标题】:C out of header in C++C ++中的标头中的C
【发布时间】:2013-06-22 19:57:19
【问题描述】:

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

【问题讨论】:

标签: c++


【解决方案1】:

虽然我测试了您的代码示例,并且它使用 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,即使强制 仅在标头中定义内联函数,强烈建议这样做,所以由于一个定义规则,您不必将内联正文复制到将包含该标题的每个翻译单元。

正如维基百科在topic 上所说:

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

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

HTH

【讨论】:

  • 实现在.cpp 文件中,对吗?所以不需要在那里测试__cplusplus。此外,正确的测试是#if __cplusplus。一些 C/C++ 编译器在 C 语言模式下运行时会#define __cplusplus 0
  • 虽然我认为 C++ 文件保留#if define __cplusplus 并没有什么坏处,你说得对,但我没有想到可能仍然是真的第二种情况(虽然我不知道哪些编译器会这样做)。
  • 有趣的事情:刚刚得到一个编辑添加#ifdef __cplusplus :-)
  • 标头应使用#if __cplusplus。实现文件只需要无条件extern "C"
  • “所以编译器不会对这些函数进行名称修改” - 相反,编译器会对这些函数使用 C 修改。
【解决方案2】:

这里实际上有两个问题:

  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中的预处理器指令吗?

【讨论】:

  • 关于你的问题,这不是#的错,而是因为当你在列表之后执行代码块时它会出错。只需在代码块前添加一些文本,就不会乱了。参考我的编辑。
猜你喜欢
  • 2011-06-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-02
  • 2020-06-11
  • 2017-05-27
  • 1970-01-01
  • 2015-07-17
相关资源
最近更新 更多