c语言调用编译器的函数 在编译器调用函数时选择函数的依据( 二 )


如果drawLine位于C运行库中 , 那就是一个不同的故事了 。你的C++源文件包含的头文件中申明为:
void drawLine(int x1, int y1, int x2, int y2);
代码体中通常也是调用drawLine 。每个这样的调用都被编译器转换为调用名变换后的函数,所以写下的是
drawLine(a, b, c, d);// call to unmangled function name
obj文件中调用的是:
xyzzy(a, b, c, d);// call to mangled function mame
但如果drawLine是一个C函数 , obj文件(或者是动态链接库之类的文件)中包含的编译后的drawLine函数仍然叫drawLine;没有名变换动作 。当你试图将obj文件链接为程序时 , 将得到一个错误,因为链接程序在寻找一个叫xyzzy的函数,而没有这样的函数存在 。
要解决这个问题,你需要一种方法来告诉C++编译器不要在这个函数上进行名变换 。你不期望对用其它语言写的函数进行名变换,如C、汇编、Fortran、LISP、Forth或其它 。(是的,这“其它”中应该包括COBOL,但那时你将得到什么?(Yes, what-have-you would include COBOL, but then what would you have? ))总之,如果你调用一个名字为drawLine的C函数,它实际上就叫drawLine , 你的obj文件应该包含这样的一个引用 , 而不是引用进行了名变换的版本 。
要禁止名变换 , 使用C++的extern 'C'指示:
// declare a function called drawLine; don't mangle
// its name
extern "C"
void drawLine(int x1, int y1, int x2, int y2);
不要以为有一个extern 'C',那么就应该同样有一个extern 'Pascal'和extern 'FORTRAN' 。没有,至少在C++标准中没有 。不要将extern 'C'看作是申明这个函数是用C语言写的 , 应该看作是申明在个函数应该被当作好象C写的一样而进行调用 。(使用术语就是,extern 'C'意思是这个函数有C链接,但这个意思表达实在不怎么清晰 。不管如何 , 它总意味着一件事:名变换被禁止了 。)
例如,如果不幸到必须要用汇编写一个函数,你也可以申明它为extern 'C':
// this function is in assembler - don't mangle its name
extern "C" void twiddleBits(unsigned char bits);
你甚至可以在C++函数上申明extern 'C' 。这在你用C++写一个库给使用其它语言的客户使用时有用 。通过禁止这些C++函数的名变换,你的客户可以使用你选择的自然而直观的名字,而不用使用你的编译生成的变换后的名字:
// the following C++ function is designed for use outside
// C++ and should not have its name mangled
extern "C" void simulate(int iterations);
经常,你有一堆函数不想进行名变换,为每一个函数添加extern 'C'是痛苦的 。幸好,这没必要 。extern 'C'可以对一组函数生效,只要将它们放入一对大括号中:
extern "C" {// disable name mangling for
// all the following functions
void drawLine(int x1, int y1, int x2, int y2);
void twiddleBits(unsigned char bits);
void simulate(int iterations);
...
}
这样使用extern 'C'简化了维护那些必须同时供C++和C使用的头文件的工作 。当用C++编译时,你应该加extern 'C',但用C编译时,不应该这样 。通过只在C++编译器下定义的宏__cplusplus,你可以将头文件组织得这样:
#ifdef __cplusplus
extern "C" {
#endif
void drawLine(int x1, int y1, int x2, int y2);
void twiddleBits(unsigned char bits);
void simulate(int iterations);
...
#ifdef __cplusplus
}
#endif
顺便提一下,没有标准的名变换规则 。不同的编译器可以随意使用不同的变换方式,而事实上不同的编译器也是这么做的 。这是一件好事 。如果所有的编译器使用同样的变换规则 , 你会误认为它们生成的代码是兼容的 。现在,如果混合链接来自于不同编译器的obj文件,极可能得到应该链接错误,因为变换后的名字不匹配 。这个错误暗示了,你可能还有其它兼容性问题,早些找到它比以后找到要好 。

推荐阅读