通常,在C语言的头文件中经常可以看到类似下面这种形式的代码:
- #ifdef __cplusplus
- extern "C" {
- #endif
- /**** some declaration or so *****/
- #ifdef __cplusplus
- }
- #endif /* end of __cplusplus */
- int foo(int a, int b);
首先假设有下面这样三个文件:
- /* file: test_extern_c.h */
- #ifndef __TEST_EXTERN_C_H__
- #define __TEST_EXTERN_C_H__
- #ifdef __cplusplus
- extern "C" {
- #endif
- /*
- * this is a test function, which calculate
- * the multiply of a and b.
- */
- extern int ThisIsTest(int a, int b);
- #ifdef __cplusplus
- }
- #endif /* end of __cplusplus */
- #endif
在这个头文件中只定义了一个函数,ThisIsTest()。这个函数被定义为一个外部函数,可以被包括到其它程序文件中。假设ThisIsTest()函数的实现位于test_extern_c.c文件中:
- /* test_extern_c.c */
- #include "test_extern_c.h"
- int ThisIsTest(int a, int b)
- {
- return (a + b);
- }
可以看到,ThisIsTest()函数的实现非常简单,就是将两个参数的相加结果返回而已。现在,假设要从CPP中调用ThisIsTest()函数:
- /* main.cpp */
- #include "test_extern_c.h"
- #include <stdio.h>
- #include <stdlib.h>
- class FOO {
- public:
- int bar(int a, int b)
- {
- printf("result=%i ", ThisIsTest(a, b));
- }
- };
- int main(int argc, char **argv)
- {
- int a = atoi(argv[1]);
- int b = atoi(argv[2]);
- FOO *foo = new FOO();
- foo->bar(a, b);
- return(0);
- }
在这个CPP源文件中,定义了一个简单的类FOO,在其成员函数bar()中调用了ThisIsTest()函数。下面看一下如果采用gcc编译test_extern_c.c,而采用g++编译main.cpp并与test_extern_c.o连接会发生什么情况:
- [cyc@cyc src]$ gcc -c test_extern_c.c
- [cyc@cyc src]$ g++ main.cpp test_extern_c.o
- [cyc@cyc src]$ ./a.out 4 5
- result=9
- /* test_extern_c.h */
- #ifndef __TEST_EXTERN_C_H__
- #define __TEST_EXTERN_C_H__
- //#ifdef __cplusplus
- //extern "C" {
- //#endif
- /*
- * this is a test function, which calculate
- * the multiply of a and b.
- */
- extern int ThisIsTest(int a, int b);
- //#ifdef __cplusplus
- // }
- //#endif /* end of __cplusplus */
- #endif
除此之外,其它文件不做任何的改变,仍然采用同样的方式编译test_extern_c.c和main.cpp文件:
- [cyc@cyc src]$ gcc -c test_extern_c.c
- [cyc@cyc src]$ g++ main.cpp test_extern_c.o
- /tmp/cca4EtJJ.o(.gnu.linkonce.t._ZN3FOO3barEii+0x10): In function `FOO::bar(int, int)':
- : undefined reference to `ThisIsTest(int, int)'
- collect2: ld returned 1 exit status
在编译main.cpp的时候就会出错,连接器ld提示找不到对函数ThisIsTest()的引用。
为了更清楚地说明问题的原因,我们采用下面的方式先把目标文件编译出来,然后看目标文件中到底都有些什么符号:
- [cyc@cyc src]$ gcc -c test_extern_c.c
- [cyc@cyc src]$ objdump -t test_extern_c.o
- test_extern_c.o: file format elf32-i386
- SYMBOL TABLE:
- 00000000 l df *ABS* 00000000 test_extern_c.c
- 00000000 l d .text 00000000
- 00000000 l d .data 00000000
- 00000000 l d .bss 00000000
- 00000000 l d .comment 00000000
- 00000000 g F .text 0000000b ThisIsTest
- [cyc@cyc src]$ g++ -c main.cpp
- [cyc@cyc src]$ objdump -t main.o
- main.o: file format elf32-i386
- SYMBOL TABLE:
- 00000000 l df *ABS* 00000000 main.cpp
- 00000000 l d .text 00000000
- 00000000 l d .data 00000000
- 00000000 l d .bss 00000000
- 00000000 l d .rodata 00000000
- 00000000 l d .gnu.linkonce.t._ZN3FOO3barEii 00000000
- 00000000 l d .eh_frame 00000000
- 00000000 l d .comment 00000000
- 00000000 g F .text 00000081 main
- 00000000 *UND* 00000000 atoi
- 00000000 *UND* 00000000 _Znwj
- 00000000 *UND* 00000000 _ZdlPv
- 00000000 w F .gnu.linkonce.t._ZN3FOO3barEii 00000027 _ZN3FOO3barEii
- 00000000 *UND* 00000000 _Z10ThisIsTestii
- 00000000 *UND* 00000000 printf
- 00000000 *UND* 00000000 __gxx_personality_v0
可以看到,采用gcc编译了test_extern_c.c之后,在其目标文件test_extern_c.o中的有一个ThisIsTest符号,这个符号就是源文件中定义的ThisIsTest()函数了。而在采用g++编译了main.cpp之后,在其目标文件main.o中有一个_Z10ThisIsTestii符号,这个就是经过g++编译器“粉碎”过后的函数名。其最后的两个字符i就表示第一参数和第二参数都是整型。而为什么要加一个前缀_Z10我并不清楚,但这里并不影响我们的讨论,因此不去管它。显然,这就是原因的所在,其原理在本文开头已作了说明。
- [cyc@cyc src]$ gcc -c test_extern_c.c
- [cyc@cyc src]$ objdump -t test_extern_c.o
- test_extern_c.o: file format elf32-i386
- SYMBOL TABLE:
- 00000000 l df *ABS* 00000000 test_extern_c.c
- 00000000 l d .text 00000000
- 00000000 l d .data 00000000
- 00000000 l d .bss 00000000
- 00000000 l d .comment 00000000
- 00000000 g F .text 0000000b ThisIsTest
- [cyc@cyc src]$ g++ -c main.cpp
- [cyc@cyc src]$ objdump -t main.o
- main.o: file format elf32-i386
- SYMBOL TABLE:
- 00000000 l df *ABS* 00000000 main.cpp
- 00000000 l d .text 00000000
- 00000000 l d .data 00000000
- 00000000 l d .bss 00000000
- 00000000 l d .rodata 00000000
- 00000000 l d .gnu.linkonce.t._ZN3FOO3barEii 00000000
- 00000000 l d .eh_frame 00000000
- 00000000 l d .comment 00000000
- 00000000 g F .text 00000081 main
- 00000000 *UND* 00000000 atoi
- 00000000 *UND* 00000000 _Znwj
- 00000000 *UND* 00000000 _ZdlPv
- 00000000 w F .gnu.linkonce.t._ZN3FOO3barEii 00000027 _ZN3FOO3barEii
- 00000000 *UND* 00000000 ThisIsTest
- 00000000 *UND* 00000000 printf
- 00000000 *UND* 00000000 __gxx_personality_v0
注意到这里和前面有什么不同没有,可以看到,在两个目标文件中,都有一个符号ThisIsTest,这个符号引用的就是ThisIsTest()函数了。显然,此时在两个目标文件中都存在同样的ThisIsTest符号,因此认为它们引用的实际上同一个函数,于是就将两个目标文件连接在一起,凡是出现程序代码段中有ThisIsTest符号的地方都用ThisIsTest()函数的实际地址代替。另外,还可以看到,仅仅被extern "C" {}包围起来的函数采用这样的目标符号形式,对于main.cpp中的FOO类的成员函数,在两种编译方式后的符号名都是经过“粉碎”了的。
因此,综合上面的分析,我们可以得出如下结论:采用extern "C" {} 这种形式的声明,可以使得CPP与C之间的接口具有互通性,不会由于语言内部的机制导致连接目标文件的时候出现错误。需要说明的是,上面只是根据我的试验结果而得出的结论。由于对于CPP用得不是很多,了解得也很少,因此对其内部处理机制并不是很清楚,如果需要深入了解这个问题的细节请参考相关资料。
备注:
1. 对于要在cpp中使用的在c文件中写好的函数func(),只需要在c文件的头文件中添加extern "C"声明就可以了。比如:extern "C" func() { ...}
当然,可以使用
#ifdef __cplusplus
extern "C" {
#endif
和
#ifdef __cplusplus
}
#endif