为什么需要 extern "C" ?

转载来自:https://zhuanlan.zhihu.com/p/114669161

 

假设我们有下面三个文件:

// util.h
int add(int, int);
// util.c
int add(int a, int b)
{
    return a + b;
}
// main.c
#include "util.h"

int main(int argc, char *argv[])
{
    int sum = add(1, 2);
    return 0;
}

 

使用 gcc 编译,生成的符号表内容分别是下面这个样子:

// main

// util.o

可以看到add函数在两个符号表中的符号都是_add.

我们再看看使用g++单独编译 main.c 文件,生成的main中符号表内容:

现在add函数在符号表中的符号是__Z3addii。这是由于g++编译器的Name mangling导致的。

为什么要name mangling?因为C++支持函数重载(函数名称相同,参数列表不同)。
需要为重载的函数生成唯一的符号表符号,以便链接器在链接的时候可以找到正确的函数地址。

我们尝试对add函数进行重载,在 main.c 中添加重载函数

```int add(double, double);```

#include "util.h"

int add(double a, double b) {
    return a + b;
}

int main(int argc, char *argv[])
{
    int sum = add(1, 2);
    sum = add(1.0, 2.0);

    return 0;
}

 

再次编译 main.c , 符号表发生了变化。相同名称的 add 函数,在经过 g++ 编译之后产生了两个不同的符号。

__Z3adddd,表示参数列表为两个double类型的add函数,(3代表函数名称是3个字符).

__Z3addii,表示参数列表为两个int类型的add函数。

由于gcc和g++生成符号表的方式不同,导致在C++项目中如果使用gcc编译的C模块,会出现链接错误。因为链接器会去C模块的(util.o)文件中查找,__Z3addii这样的符号。显然在C模块的目标文件(util.o)中,不存在__Z3addii这样的符号,它有的只是_add。最终导致链接错误,原因是”符号未定义“。 找不到”__Z3addii“,这个符号的定义

那我们如果想在C++项目中使用gcc编译的C模块该怎么办呢?

// util.h
extern "C"
{

    int add(int, int);
}

通过extern "C",告诉g++编译器,不要对这些函数进行Name mangling,按照C编译器的方式去生成符号表符号。这样在main.c的目标文件(.o)中,参数列表为两个int类型的add函数名称为_add。链接器可以正确找到util.o中的add函数(他们都是_add)。注意参数列表为两个double类型的add函数名称还是__Z3adddd。

 

使用 extern ”C“ 的常见情况是使用第三方提供的编译好的静态链接库(.a/.lib),动态链接库(.so/.dll)。通常我们会拿到一个头文件和对应的编译好的库文件。

在头文件中通过条件编译引入 extern "C"。

#ifdef __cplusplus
extern "C" {
#endif

int add(int, int);

#ifdef __cplusplus
}
#endif 

另外在C程序中调用C++函数也需要 extern "C" 的参与。

posted @ 2022-06-30 10:38  冰糖葫芦很乖  阅读(85)  评论(0编辑  收藏  举报