在C++代码中,如果加上类似于:
extern "C" {<br /> int func(int);<br /> int var;<br />}
意思是告诉编译器,上面extern “C”中的这段代码,要当作C 语言处理。
但是具体深层含义,我自己是从来没去关注过,知道看了《程序员的自我修养 –链接,装载与库》,才更加清楚一点,记录如下(后半部分内容主要摘抄自原文):
1。首先你要了解,代码编译后的,关于符号(symbol)的知识
2。其次,你要大概知道代码从源码到可执行文件所要经历的步骤,及其每一步的作用。
3。此处的extern "C"就是,不论你的C++代码被编译成什么类型的库(dll,lib,.a,.so),所用到的编译器都会将源码里面的函数,变量等,根据C++编译器自己(目前主要分GNU GCC和Visual C++)的名称修饰规则,将原先名称的变量,变成另外一系列的名称,便于之后的连接器的链接。
书中给出一个例子:
[关于C++的名称修饰的细节]
int func(int);<br />float func(float); class C {<br />int func(int);<br />class C2 {<br />int func(int);<br />};<br />}; namespace N {<br />int func(int);<br />class C {<br />int func(int);<br />};<br />} |
中的这6个func函数,经过名称修饰后,就变成:<br /><strong>(1)对于GUN C++:</strong>
函数签名 | 修饰后名称(符号名) |
int func(int) | _Z4funci |
float func(float) | _Z4funcf |
int C::func(int) | _ZN |
int C::C2::func(int) | _ZN |
int N::func(int) | _ZN1N4funcEi |
int N::C::func(int) | _ZN1N |
GNU C++名称修饰规则示例
GCC的基本C++名称修饰方法如下:所有的符号都以"_Z"开头,对于嵌套的名字(在名称空间或在类里面的),后面紧跟"N",然后是各个名称空 间和类的名字,每个名字前是名字字符串长度,再以"E"结尾。比如N::C::func经过名称修饰以后就是_ZN1N1C4funcE。对于一个函数来 说,它的参数列表紧跟在"E"后面,对于int类型来说,就是字母"i"。所以整个N::C::func(int)函数签名经过修饰为 _ZN1N1C4funcEi。更为具体的修饰方法我们在这里不详细介绍,有兴趣的读者可以参考GCC的名称修饰标准。幸好这种名称修饰方法我们平时程序 开发中也很少手工分析名称修饰问题,所以无须很详细地了解这个过程。binutils里面提供了一个叫"c++filt"的工具可以用来解析被修饰过的名 称,比如:
$ c++filt _ZN1N1C4funcEi
N::C::func(int)
<strong>(2)对于Visual C++:</strong>
函数签名 | 修饰后名称 |
int func(int) | ?func@@YAHH@Z |
float func(float) | ?func@@YAMM@Z |
int C::func(int) | ?func@C@@AAEHH@Z |
int C::C2::func(int) | ?func@C2@C@@AAEHH@Z |
int N::func(int) | ?func@N@@YAHH@Z |
int N::C::func(int) | ?func@C@N@@AAEHH@Z |
Visual C++名称修饰规则示例
Visual C++的名称修饰规则并没有对外公开,当然,一般情况下我们也无须了解这套规则。此处只是学习,了解一下即可。
作者以int N::C::func(int)这个函数签名来猜测Visual C++的名称修饰规则:
修饰后名字由"?"开头,接着是函数名由"@"符号结尾的函数名;后面跟着由"@" 结尾的类名"C"和名称空间"N",再一个"@"表示函数的名称空间结束;第一个"A"表示函数调用类型为"__cdecl"(函数调用类型我们将在第4 章详细介绍),接着是函数的参数类型及返回值,由"@"结束,最后由"Z"结尾。
可以看到函数名、参数的类型和名称空间都被加入了修饰后名称,这样编译器 和链接器就可以区别同名但不同参数类型或名字空间的函数,而不会导致link的时候函数多重定义。
但是有时候可能须要将一个修饰后名字转换成函数签名,比如在链接、调试程 序的时候可能会用到。Microsoft提供了一个UnDecorateSymbolName()的API,可以将修饰后名称转换成函数签名。下面这段代 码使用UnDecorateSymbolName()将修饰后名称转换成函数签名:
/* 2-4.c
* Compile: cl 2-4.c /link Dbghelp.lib
* Usage: 2-4.exe DecroatedName
*/
#include <Windows.h>
#include <Dbghelp.h>int main( int argc, char* argv[] )
{
char buffer[256]; if(argc == 2)
{
UnDecorateSymbolName( argv[1], buffer, 256, 0 );
printf( buffer );
}
else
{
printf( "Usage: 2-4.exe DecroatedNamen" );
} return 0;
}
[extern “C”的具体含义]
所以,如果加上了 extern “C”,那么,C++编译器会将在extern "C" 的大括号内部的代码当作C语言代码处理。也就是,对于上面代码中,C++的名称修饰机制将不会起作用。这就使得,在其后的链接过程中,所能看到的变量,就是C 语言里面中的类似的变量了。在Visual C++平台下会将C语言的符号进行修饰,所以上述代码中的func和var的修饰后符号分别是_func和_var;但是在Linux版本的GCC编译器 下却没有这种修饰,extern "C"里面的符号都为修饰后符号,即前面不用加下划线。如果单独声明某个函数或变量为C语言的符号,那么也可以使用如下格式:
extern "C" int func(int);<br />extern "C" int var; |
上面的代码声明了一个C语言的函数func和变量var。
A。关于名称修饰规则的小实验
我们可以使用上述的机制来做一个小实验:
// ManualNameMangling.cpp<br />// g++ ManualNameMangling.cpp -o ManualNameMangling #include <stdio.h> namespace myname {<br />int var = 42;<br />} extern "C" double _ZN6myname3varE; int main()<br />{<br />printf( "%dn", _ZN6myname3varE );<br />return 0;<br />} |
上 面的代码中,我们在myname的名称空间中定义了一个全局变量var。根据我们所掌握的GCC名称修饰规则,这个变量修饰后的名称 为"_ZN6myname3varE",然后我们手工使用extern "C"的方法声明一个外部符号_ZN6myname3varE,并将其打印出来。我们使用GCC来编译这个程序并且运行它,我们就可以得到程序输出为 42:
$ g++ ManaulNameMangling.cpp -o ManualNameMangling<br />$ ./ManualNameMangling<br />42<br /><br /><strong>B。常见情况示例解析</strong>
很多时候我们会碰到有些头文件声明了一些C语言的函数和全局变量,但是这个头文件可能会被C语言代码或C++代码包含。比如很常见的,我们的C语言库函数中的string.h中声明了memset这个函数,它的原型如下:
void *memset (void *, int, size_t); |
如 果不加任何处理,当我们的C语言程序包含string.h的时候,并且用到了memset这个函数,编译器会将memset符号引用正确处理;但是在 C++语言中,编译器会认为这个memset函数是一个C++函数,将memset的符号修饰成_Z6memsetPvii,这样链接器就无法与C语言库 中的memset符号进行链接。所以对于C++来说,必须使用extern "C"来声明memset这个函数。但是C语言又不支持extern "C"语法,如果为了兼容C语言和C++语言定义两套头文件,未免过于麻烦。幸好我们有一种很好的方法可以解决上述问题,就是使用C++的 宏"__cplusplus",C++编译器会在编译C++的程序时默认定义这个宏,我们可以使用条件宏来判断当前编译单元是不是C++代码。具体代码如 下:
#ifdef __cplusplus<br />extern "C" {<br />#endif<br /><br />void *memset (void *, int, size_t); #ifdef __cplusplus<br />}<br />#endif |
如果当前编译单元是C++代码,那么memset会在extern "C"里面被声明;如果是C代码,就直接声明。上面这段代码中的技巧几乎在所有的系统头文件里面都被用到。
[总结]
加了extern "C" 后,就是让C++编译器知道,此部分的代码,要用C 语言的名称修饰规则处理。
[引用]
1。程序员的自我修养:链接、装载与库 (含介绍 以及 第三章和第十一章的内容)
http://book.51cto.com/art/200904/120986.htm
转载请注明:在路上 » [学习记录]extern “C“ 的深层次含义