链接指示符extern “C”
extern“C”的使用方式
如果程序员希望调用其他程序设计语言(尤其是C)写的函数,那么,调用函数时必须告诉编译器使用不同的要求。例如,当这样的函数被调用时,函数名或参数排列的顺序可能不同,无论是C++函数调用它,还是用其他语言写的函数调用它。
程序员用链接指示符(linkage directive)告诉编译器,该函数是用其他的程序设计语言编写的,链接指示符有两种形式:既可以是单一语句(single statement)形式,也可以是复合语句(compound statement)形式:
// 单一语句形式的链接指示符
extern "C" void exit(int);
// 复合语句形式的链接指示符
extern "C" {
int printf( const char* ... );
int scanf( const char* ... );
}
// 复合语句形式的链接指示符
extern "C" {
#include <cmath>
}
链接指示符的第一种形式由关键字extern后跟一个字符串常量以及一个“普通”的函数声明构成。虽然函数是用另外一种语言编写的,但调用它仍然需要类型检查。例如,编译器会检查传递给函数exit()的实参的类型是否是int,或者能够隐式地转换成int 型。
多个函数声明可以用花括号包含在链接指示符复合语句中,这是链接指示符的第二种形式。花招号被用作分割符,表示链接指示符应用在哪些声明上。在其他意义上该花括号被忽略,所以在花括号中声明的函数名对外是可见的,就好像函数是在复合语句外声明的一样。例如,在前面的例子中,复合语句extern "C"表示函数printf()和scanf()是在C 语言中写的函数。因此,这个声明的意义就如同printf()和scanf()是在extern "C"复合语句外面声明的一样。
当复合语句链接指示符的括号中含有#include 时,在头文件中的函数声明都被假定是用链接指示符的程序设计语言所写的。在前面的例子中,在头文件<cmath>中声明的函数都是C函数。
链接指示符不能出现在函数体中,下列代码段将会导致编译错误:
int main()
{
// 错误: 链接指示符不能出现在函数内
extern "C" double sqrt( double );
double getValue(); //ok
double result = sqrt ( getValue() );
//...
return 0;
}
如果把链接指示符移到函数体外,程序编译将无错误:
extern "C" double sqrt( double );
int main()
{
double getValue(); //ok
double result = sqrt ( getValue() );
//...
return 0;
}
但是,把链接指示符放在头文件中更合适。在那里,函数声明描述了函数的接口所属。
在这里我们只看到为C语言提供的链接指示:extern "C"。extern "C"是惟一被保证由所有C++实现都支持的。每个编译器实现都可以为其环境下常用的语言提供其他链接指示。例如extern "Ada"可以用来声明是用Ada 语言写的函数,extern "FORTRAN"用来声明是用FORTRAN 语言写的函数,等等…因为其他的链接指示随着具体实现的不同而不同,所以建议读者查看编译器的用户指南,以获得其他链接指示符的进一步信息。
深入理解extern "C"
要明白为何需要使用extern "C",还得从C++中对函数的重载处理开始说起。作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:
void foo( int x, int y );
该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了类似的机制,生成的新名字称为“mangled name”)。_foo_int_int这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。例如,在C++中,函数void foo( int x, int y )与void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float。
比如下面的一段简单的函数,我们看看加入和不加入extern "C"产生的汇编代码都有哪些变化:
int f(void)
{
return 1;
}
在加入extern "C"的时候产生的汇编代码是:
.file "test.cxx"
.text
.align 2
.globl _f
.def _f; .scl 2; .type 32; .endef
_f:
pushl %ebp
movl %esp, %ebp
movl $1, %eax
popl %ebp
ret
但是不加入了extern "C"之后
.file "test.cxx"
.text
.align 2
.globl __Z1fv
.def __Z1fv; .scl 2; .type 32; .endef
__Z1fv:
pushl %ebp
movl %esp, %ebp
movl $1, %eax
popl %ebp
ret
两段汇编代码同样都是使用gcc -S命令产生的,所有的地方都是一样的,唯独是产生的函数名,一个是_f,一个是__Z1fv。
明白了加入与不加入extern "C"之后对函数名称产生的影响,我们继续我们的讨论:为什么需要使用extern "C"呢?C++之父在设计C++之时,考虑到当时已经存在了大量的C代码,为了支持原来的C代码和已经写好C库,需要在C++中尽可能的支持C,而extern "C"就是其中的一个策略。
参考资料:
《C++ primer》3th
http://tech.163.com/06/0118/09/27O66HCC0009159Q.html
http://dev.yesky.com/72/3270072.shtml
http://soft.ccw.com.cn/programing/other/htm2008/20080905_496705.shtml
http://www.cnblogs.com/xulei/archive/2006/11/12/558139.html