C++中的头文件中的C是什么意思?

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 or .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;
}

1
名称修饰是一种编译器技术,用于在链接时将函数和变量的名称更改为其他名称。这通常用于实现信息隐藏、名称空间分离和重载功能。C++中使用了名称修饰,而C语言则没有。名称修饰可以使相同的函数名具有不同的参数列表,从而使函数重载成为可能。 - user529758
可能是Linker error calling C-Function from Objective-C++的重复问题。 - user529758
我刚刚测试了一下,使用g++编译器和clang编译器都可以成功编译。 - zmo
如果您删除“inline”子句,错误是否仍然存在? - Yury Schkatula
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,虽然不强制要求只在头文件中定义内联函数,但强烈建议这样做,这样你就不必把内联体复制到每个包含该头文件的翻译单元中,因为有了一个定义规则
正如维基百科在主题上所说:

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

但最终,无论是好还是坏,都取决于你的设计选择。
希望对你有所帮助。

实现在 .cpp 文件中,对吧?所以不需要在那里测试 __cplusplus。此外,正确的测试是 #if __cplusplus。一些 C/C++ 编译器在运行 C 语言模式时会将 __cplusplus 宏定义为 0。 - Ben Voigt
虽然我认为在C++文件中保留#if define __cplusplus不会有什么问题,但你是对的,我没有考虑到可能仍然存在第二种情况(尽管我不知道哪些编译器会这样做)。 - zmo
有趣的事情:刚刚收到一个编辑,将 #ifdef __cplusplus 添加回去了 :-) - zmo
头文件应该使用 #if __cplusplus。实现文件只需要无条件的 extern "C" - Ben Voigt
因此编译器不会对这些函数进行名称混淆,而是使用C语言的名称混淆。 - Pete Becker

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

关于你的问题,这不是#的错,而是因为当你在列表后面添加代码块时会出现错误。只需在代码块前添加一些文本,就不会出现混乱。请参考我的编辑。 - zmo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接