程序设计语言

深度探索extern “C”

2009年5月28日 阅读(310)

c和c++的函数名处理
首先看函数void f(){int i = 1;}
在c和c++中分别被编译成
.globl _Z1fv
    .type    _Z1fv, @function
.globl f
    .type    f, @function

观察如下文件a.c:
extern void f(int);
int main(){
  f(1);    
  return 1;
}
假设f定义在另一个文件b.c中:
void f(int x){
    int i = 1;
}

对于a.c,c++会根据f的声明,生成如下指令:
    call    _Z1fi
对于b.c,如果也用g++编译的是没有问题的:
.globl _Z1fi
    .type    _Z1fi, @function
这样链接时可以保证找到这个函数。但是如果b.c是由gcc编译的:
.globl f
    .type    f, @function
这样就是有问题的了,因为两个函数名称不一样了,因为cpp为了支持重载,对函数名称进行了mangle.

extern "C"
extern "C"就是为了可以让c++调用c函数,而设计的,同时也可以让c调用c++函数。extern表明是全局的,"C"表示对这一段按照c的方式来处理。被extern "C"修饰的变量和函数是按照C语言方式编译和连接的。比如对于上面的这个,修改成这样
extern "C" void f(int);
int main(){
  f(1);    
  return 1;
}

这样经过g++编译后,f(1);变成了call    f,这样就可以在链接时链接到c中的那个f函数了。

extern "C"不仅可以让c++调用c函数,也可以让c调用c++函数。因为它不仅可以包含一个定义在c里的声明,也可包含一个定义在c++里的函数的声明。通过包含一个定义在c的声明,可以让c++对所有的被调用函数按照c的方式解析,这样就可以使被调用函数与c中定义的名称一致。而通过包含一个定义在c++里函数声明,这样就可以让c++中定义的函数按照c的方式编译,同时调用解析也按照这个c中的方式,这样从c里就可以访问c++里的函数了。
比如上面的b.c里,如果修改成这样
extern "C" {
void f(int x){
    int i = 1;
}
}
g++会产生如下函数定义:
.globl f
    .type    f, @function
这样c就可以直接调用它了。说到这里,需要注意extern "C"只是c++中的语法,c里面无法使用。另外实际上只需要对f的说明包含一个extern "C" ,也就是说b.c可以写成这样:

extern "C" void f(int);
void f(int x){
int i =1;
}
不使用extern "C"的方法
但是考虑如果现在的c++是个库函数而我们又不能修改,比如给它加上extern "C",那我们能否让c调用c++函数呢,可以想到的一个方法就是让c直接使用mangled后的名称调用。虽然这样的用法很诡异,但是的确是可行的。
对于上面那个简单的f函数只要在a.c里这样调用就可以了:
extern void _Z1fi(int);
int main(){
  _Z1fi(1);    
  return 1;
}

但有时候可能会发生一些比如__gxx_personality_v0这样的编译错误,比如f函数如果是这样的:
#include <stdio.h>
void f(int x){
    printf("ok hello");
}

有时候如果源代码的文件后缀名和代码的语法不相符合,比如用C的语法的文件,选择了cpp这样的后缀名(或者<大写C>这样的后缀名),会出现这个错误。这里的原因所用gcc链接了cpp的文件。加上 -lstdc++ 就可以编译成功了, gcc -lstdc++ -o  a a.c b.o。再分析一下,主要是使用了gcc链接C++的代码时,这个c++代码使用了c++的库函数,所以还需要链接上c++库。所以连接的时候没有加-lstdc++参数,导致c++的库函数没有被连接进来。加-lstdc++就ok了。

一个惯用法
另一方面我们可以发现一般的标准库函数这样写:

#ifndef __INCvxWorksh

#define __INCvxWorksh 

#ifdef __cplusplus

extern "C" {

#endif 

/*…*/ 

#ifdef __cplusplus

}

#endif 

#endif /* __INCvxWorksh */

实际上这样写,就让该标准库头文件可以同时被c和c++使用。比如这样处理下标准c库的头文件,就可以让c++直接使用它们了。同时,也让这样生成的库函数对于c调用来说是兼容的,允许c对它的调用。实际上观察汇编文件中对于c标准库的调用,我们可以看到c++里继承自c的标准库函数,依然保持了c里的函数名称,可见它们都进行了这种extern “C”的修饰。

转载请注明作者:phylips@bmy 出处:http://duanple.blog.163.com/blog/static/70971767200942823319221/

You Might Also Like