# externC

# 1.为什么要使用externC

一句话总结,C++支持函数重载,C语言不支持函数重载,在生成的C++编译文件中函数名会根据参数进行混淆(mangle),而C语言的编译文件不会被混淆,所以在C++程序中链接C语言的函数动态库时需要使用externC来保证函数签名的正确性。

/// main.cpp

void f() {}
void g();

extern "C" {
    void ef() {}
    void eg();
}

/* Prevent g and eg from being optimized away. */
void h() { g(); eg(); }

如上代码,使用命令编译后查看目标文件符号:

g++ -c -std=c++11 -Wall -Wextra -pedantic -o main.o main.cpp
readelf -s main.o
# Symbol table '.symtab' contains 13 entries:
#    Num:    Value          Size Type    Bind   Vis      Ndx Name
#      0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
#      1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS test_externC.cc
#      2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 
#      3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 
#      4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 
#      5: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
#      6: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
#      7: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 
#      8: 0000000000000000     7 FUNC    GLOBAL DEFAULT    1 _Z1fv
#      9: 0000000000000007     7 FUNC    GLOBAL DEFAULT    1 ef
#     10: 000000000000000e    17 FUNC    GLOBAL DEFAULT    1 _Z1hv
#     11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _Z1gv
#     12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND eg

可以看到被extern "C"修饰的函数ef/gf函数名没有被混淆mangle,而未被函数名包围的按C++的编译规则被混淆了。

使用c++filt _Z1fv命令可以反混淆(unmangle)函数名称,会发现_Z1fv/_Z1hv/_Z1gv对应的就是f/g/h

c++filt是一个用于解码(反编译)低级别的C++Java符号名称的工具,用于将修饰后的函数名映射回源函数名。

# 使用externC的一个例子

先创建一个C语言的so库:

// add.h

int add(int x, int y);

// add.c

#include <add.h>

int add(int x, int y)
{
    return x + y;
}
/// 编译成so库
/// gcc -fPIC -shared add.c -o libadd.so

通过上面的方式编译的so文件中存在的函数符号名为add,不包含参数名称修饰,因此无法直接被c++程序链接

// main.cc

#include <add.h>

extern "C" {
    printf(fmt, ...);
}

int main(int argc, char** argv)
{
    int i = 1, j = 10;
    printf("%d\n", add(i, j));
    return 0;
}
/// gcc main.cc -o et -I ./ -L ./ -ladd
/// 报错
/// /usr/bin/ld: /tmp/ccxcRKM6.o: in function `main':
/// main.cc:(.text+0x2f): undefined reference to `add(int, int)'
/// collect2: error: ld returned 1 exit status

要想在c++中使用C语言编译的库,需要在C语言库的头文件中告诉C++哪些代码需要使用C的方式来编译,上面例子将add.h文件改成如下格式即可:

/// add.h
#ifdef __cplusplus
extern "C" {
#endif
    int add(int x, int y);

#ifdef __cplusplus
}
#endif

如此就能正常编译运行了。

# reference

1.https://www.geeksforgeeks.org/extern-c-in-c/ (opens new window)
2.https://stackoverflow.com/questions/1041866/what-is-the-effect-of-extern-c-in-c (opens new window)