extern “C” 陷阱

20 篇文章 0 订阅
9 篇文章 0 订阅

extern “C” 陷阱

extern “C”通常使用在C++中,由于C++支持函数重载,命名空间等技术,故C++编译器在编译C++代码时会对函数进行签名,也就是说编译后的函数名将发生变化。例如我们有如下的代码:

test.cpp

int fun(int a)

{

///nothing.

}

  在Ubuntu下用g++编译这个cpp文件获取它的目标文件test.o,命令:gcc -c test.cpp我们可以用GNU binutils工具readelf来查看test.o中的符号信息,命令:readelf -s test.o

输出如下:

可以看到所有的符号中并没有出现fun,这是因为g++对函数进行了签名,唯一和fun相似的符号就是_Z3funi,那么这个符号是否就是test.cpp中的fun函数呢?我们可以用c++filt(用来查看符号的未被签名前的原型)来进行验证:c++filt _Z3funi


可以看到在test.o中的符号_Z3funi对应的就是test.cpp中的fun函数。如果我们要阻止c++编译器这种签名机制,extern “C”就派上了用场。我们在fun前添加extern “C”。

extern “C” int fun(int a)

{

///nothing.

}

重新编译我们再查看test.o中的符号表:


可以看到加上extern “C” 函数fun就没有被签名。

在日常开发中extern “C” 使用在C++调用C编写的动态库的接口时显示声明C++中的接口不被签名,从而能正常调用C编写的动态库。下面看一个例子:

print.c:

#include <stdio.h>

void print(int a, int b)

{

printf(“sum = %d\n”, a+b);

}

使用命令:gcc -shared -fPIC -o LibPrint.so print.c 来编译生成动态库LibPrint.so

程序主模块:

main.cpp:

extern “C” void print(int a); ///这里只有一个int参数

int main()

{

print(1);

return 0;

}

使用命令:g++ -o main main.cpp ./LibPrint.so 来编译生成可执行程序main


可以看到编译通过了,没有报任何错误。大家可能会疑惑在动态库中的函数原型为void print(int,int),但在main.cpp中函数原型为void print(int),应该要报找不到符号。程序之所以能编译通过是因为编译器在进行连接时发现print是一个外部符号(为使用了extern “C”声明,main.o模块中函数print并没有被签名),便在动态库LibPrint.so中寻找,而LibPrint中定义了该符号,故能编译通过。

最可怕的是产生的可执行程序能够运行且不会core出,因为LibPrint.so中的print函数只是跨越了自己参数栈多取了参数栈后面的一个数据(int b),而这是合法的(访问的数据还未越界,还在程序栈内)。

综上所述当我们在使用外部用C开发的第三方库需要使用extern “C”时,尤其是在第三方库更新时要确认第三方库的接口是否改变,自己程序中接口声明是否和库中一致。


  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值