转自 http://blog.csdn.net/acb0y/article/details/9026253
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”时,尤其是在第三方库更新时要确认第三方库的接口是否改变,自己程序中接口声明是否和库中一致。