在C++ 程序中调用被 C 编译器编译后的函数,为什么要加 extern “C”声明?
发布网友
发布时间:2022-04-23 20:54
我来回答
共4个回答
热心网友
时间:2023-10-09 11:24
因为c++编译时会进行名变化,而C不会,导致无法找到函数等。
要禁止名变换,使用C++的extern 'C'指示。
详见
http://dev.csdn.net/article/13/13133.shtm
Item M34:如何在同一程序中混合使用C++和C
许多年来,你一直担心编制程序时一部分使用C++一部分使用C,就如同在全部用C编程的年代同时使用多个编译器来生成程序一样。没办法多编译器编程的,除非不同的编译器在与实现相关的特性(如int和double的字节大小,传参方式)上相同。但这个问题在语言的标准化中被忽略了,所以唯一的办法就是两个编译器的生产商承诺它们间兼容。C++和C混合编程时同样是这个问题,所以在实体混合编程前,确保你的C++编译器和C编译器兼容。
确认兼容后,还有四个要考虑的问题:名变换,静态初始化,内存动态分配,数据结构兼容。
* 名变换
名变换,就是C++编译器给程序的每个函数换一个独一无二的名字。在C中,这个过程是不需要的,因为没有函数重载,但几乎所有C++程序都有函数重名(例如,流运行库就申明了几个版本的operator<<和operator>>)。重载不兼容于绝大部分链接程序,因为链接程序通常无法分辨同名的函数。名变换是对链接程序的妥协;链接程序通常坚持函数名必须独一无二。
如果只在C++范围内,名变换不会影响你。如果你你有一个函数叫drawline而编译器将它变换为xyzzy,你总使用名字drawLine,不会注意到背后的obj文件引用的是xyzzy的。
如果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文件,极可能得到应该链接错误,因为变换后的名字不匹配。这个错误暗示了,你可能还有其它兼容性问题,早些找到它比以后找到要好。
* 静态初始化
在掌握了名变换后,你需要面对一个C++中事实:在main执行前和执行后都有大量代码被执行。尤其是,静态的类对象和定义在全局的、命名空间中的或文件体中的类对象的构造函数通常在main被执行前就被调用。这个过程称为静态初始化(参见Item E47)。这和我们对C++和C程序的通常认识相反,我们一直把main当作程序的入口。同样,通过静态初始化产生的对象也要在静态析构过程中调用其析构函数;这个过程通常发生在main结束运行之后。
为了解决main()应该首先被调用,而对象又需要在main()执行前被构造的两难问题,许多编译器在main()的最开始处插入了一个特别的函数,由它来负责静态初始化。同样地,编译器在main()结束处插入了一个函数来析构静态对象。产生的代码通常看起来象这样:
int main(int argc, char *argv[])
{
performStaticInitialization(); // generated by the
// implementation
the statements you put in main go here;
performStaticDestruction(); // generated by the
// implementation
}
不要注重于这些名字。函数performStaticInitialization()和performStaticDestruction()通常是更含糊的名字,甚至是内联函数(这时在你的obj文件中将找不到这些函数)。要点是:如果一个C++编译器采用这种方法来初始化和析构静态对象,除非main()是用C++写的,这些对象将从没被初始化和析构。因为这种初始化和析构静态对象的方法是如此通用,只要程序的任意部分是C++写的,你就应该用C++写main()函数。
有时看起来用C写main()更有意义--比如程序的大部分是C的,C++部分只是一个支持库。然而,这个C++库很可能含有静态对象(即使现在没有,以后可能会有--参见Item M32),所以用C++写main()仍然是个好主意。这并不意味着你需要重写你的C代码。只要将C写的main()改名为realMain(),然后用C++版本的main()调用realMain():
extern "C" // implement this
int realMain(int argc, char *argv[]); // function in C
int main(int argc, char *argv[]) // write this in C++
{
return realMain(argc, argv);
}
这么做时,最好加上注释来解释原因。
如果不能用C++写main(),你就有麻烦了,因为没有其它办法确保静态对象的构造和析构函数被调用了。不是说没救了,只是处理起来比较麻烦一些。编译器生产商们知道这个问题,几乎全都提供了一个额外的体系来启动静态初始化和静态析构的过程。要知道你的编译器是怎么实现的,挖掘它的随机文档或联系生产商。
* 动态内存分配
现在提到动态内存分配。通行规则很简单:C++部分使用new和delete(参见Item M8),C部分使用malloc(或其变形)和free。只要new分配的内存使用delete释放,malloc分配的内存用free释放,那么就没问题。用free释放new分配的内存或用delete释放malloc分配的内存,其行为没有定义。那么,唯一要记住的就是:将你的new和delete与mallco和free进行严格的隔离。
说比做容易。看一下这个粗糙(但很方便)的strp函数,它并不在C和C++标准(运行库)中,却很常见:
char * strp(const char *ps); // return a copy of the
// string pointed to by ps
要想没有内存泄漏,strp的调用着必须释放在strp()中分配的内存。但这内存这么释放?用delete?用free?如果你调用的strp来自于C函数库中,那么是后者。如果它是用C++写的,那么恐怕是前者。在调用strp后所需要做的操作,在不同的操作系统下不同,在不同的编译器下也不同。要减少这种可移植性问题,尽可能避免调用那些既不在标准运行库中(参见Item E49和Item M35)也没有固定形式(在大多数计算机平台下)的函数。
* 数据结构的兼容性
最后一个问题是在C++和C之间传递数据。不可能让C的函数了解C++的特性的,它们的交互必须限定在C可表示的概念上。因此,很清楚,没有可移植的方法来传递对象或传递指向成员函数的指针给C写的函数。但是,C了解普通指针,所以想让你的C++和C编译器生产兼容的输出,两种语言间的函数可以安全地交换指向对象的指针和指向非成员的函数或静态成员函数的指针。自然地,结构和内建类型(如int、char等)的变量也可自由通过。
因为C++中的struct的规则兼容了C中的规则,假设“在两类编译器下定义的同一结构将按同样的方式进行处理”是安全的。这样的结构可以在C++和C见安全地来回传递。如果你在C++版本中增加了非虚函数,其内存结构没有改变,所以,只有非虚函数的结构(或类)的对象兼容于它们在C中的孪生版本(其定义只是去掉了这些成员函数的申明)。增加虚函数将结束游戏,因为其对象将使用一个不同的内存结构(参见Item M24)。从其它结构(或类)进行继承的结构,通常也改变其内存结构,所以有基类的结构也不能与C函数交互。
就数据结构而言,结论是:在C++和C之间这样相互传递数据结构是安全的--在C++和C下提供同样的定义来进行编译。在C++版本中增加非虚成员函数或许不影响兼容性,但几乎其它的改变都将影响兼容。
* 总结
如果想在同一程序下混合C++与C编程,记住下面的指导原则:
* 确保C++和C编译器产生兼容的obj文件。
* 将在两种语言下都使用的函数申明为extern 'C'。
* 只要可能,用C++写main()。
* 总用delete释放new分配的内存;总用free释放malloc分配的内存。
* 将在两种语言间传递的东西*在用C编译的数据结构的范围内;这些结构的C++版本可以包含非虚成员函数。
热心网友
时间:2023-10-09 11:24
总之,重点就是C++里面支持函数的重载,因此编译出来的.obj或者库文件里面,函数名会被加上和参数有关的签名,用来区分同样函数名参数不同的函数。然而C语言不支持重载,所以函数名里面没有这样的签名。
这样,当C语言的程序调用C++写成的库的时候,就会找不到函数。
比如,一个函数叫 void foo(int bar)之类的,可能会在c++编译成叫 foo_i之类的名字,而在c语言里面就会编译成foo,这样c语言的程序去找foo就会找不到,这样连接的时候会出错。
为了解决这个问题,引入了extrn "c"{},在这个的作用域之内,c++的函数名不会加上参数签名,和c语言的标准保持统一,就兼容c语言的程序了。
热心网友
时间:2023-10-09 11:25
说那么多废话,
其实就是表明函数可能不在C++的标准库里面,要从其它的库里面找
热心网友
时间:2023-10-09 11:25
不会利用搜索引擎解决问题,你成不了开发人员的
在c++程序中调用被c 编译器编译后的函数,为什么要加extern c
extern C 表示按照C语言方式进行连接。你如果不加会出现连接错误(XX函数未定义),因为C++语言和C语言对编译后的函数符号和外部变量符号的修饰方式不一样,代码编译过后,连接器解析外部符号的时候就会出现无法解析外部符号的错误。你加上extern C之后,C++编译器就会按照C语言的符号修饰规则对函数进行修饰...
...器编译后的函数,为什么要加 extern “C”声明?
【答案】:函数和变量被C++编译后在符号库中的名字与C语言的不同,被extern "C"修饰的变量和函数是按照C语言方式编译和连接的。由于编译后的名字不同,C++程序不能直接调用C 函数。C++提供了一个C 连接交换指定符号extern“C”来解决这个问题。
...器编译后的函数,为什么要加 extern “C”声明?
不要将extern 'C'看作是申明这个函数是用C语言写的,应该看作是申明在个函数应该被当作好象C写的一样而进行调用。(使用术语就是,extern 'C'意思是这个函数有C链接,但这个意思表达实在不怎么清晰。不管如何,它总意味着一件事:名变换被禁止了。)例如,如果不幸到必须要用汇编写一个函数,你也可以申明它为extern ...
在C++ 程序中调用被 C 编译器编译后的函数,要加 extern “C”的原 ...
【答案】: C++语言支持函数重载,C语言不支持函数重载。函数被C++编译后在库中的名字与C语言的不同。假设某个函数的原型为:voidfoo(intx,inty);该函数被C编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。C++提供了C连接交换指定符号extern“C”来解决名字匹配问题...
c语言中exte
在C语言中,"extern"关键字起着至关重要的作用。它不仅用于标识一个变量或函数的定义被放在另一个源文件中,从而指示编译器在需要时寻找其定义,实现跨文件的声明和链接,这在增强程序模块化和重用上具有重要意义。"extern"还有另一个实用场景,即在C与C++混合编程时,当C++代码调用C源文件中的函数或...
头文件与cpp文件,文件同名与否的影响
在C++ 程序中调用被 C 编译器编译后的函数,为什么要加 extern “C”? (5 分)答:C++语言支持函数重载,C 语言不支持函数重载。函数被C++编译后在库中的名字 与C 语言的不同。假设某个函数的原型为: void foo(int x, int y);该函数被C 编译器编译后在库中的名字为_foo , 而C++编译...
C语言中extern的意思是什么?
extern关键字可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。这里起到的是声明作用范围的用处。extern的另外用法是当C和C++混合编程时假如c++调用的是c源文档定义的函数或变量,那么要加extern来告诉编译器用c方式命名函数:文档A.cpp...
飞思卡尔的面试
4、在C++ 程序中调用被 C编译器编译后的函数,为什么要加 extern “C”声明?5、请简述以下两个for循环的优缺点 // 第一个for (i=0; i ing();} // 第二个if (condition){for (i=0; i for (i=0; i 优点:缺点: 优点:缺点:四、有关内存的思考题(20分)void GetMemory(char *p...
C语言:用extern和不用extern声明方法有什么区别?
在大型C程序编译过程中,这种差异是非常明显的。3.此外,extern修饰符可用于指示C或者C++函数的调用规范。比如在C++中调用C库函数,就需要在C++程序中用extern “C”声明要引用的函数。这是给链接器用的,告诉链接器在链接的时候用C函数规范来链接。主要原因是C++和C程序编译完成后在目标...
程序员面试笔试宝典的目录
7.12.4 在C++程序中调用被C编译器编译后的函数,为什么要加extern “C” 2097.12.5 两段代码共存于一个文件,编译时有选择地编译其中的一部分,如何实现 2107.13 面向对象相关 2107.13.1 面向对象与面向过程有什么区别 2107.13.2 面向对象的基本特征有哪些 2117.13.3 什么是深复制?什么是浅复制 2127.13.4 什么...