本博客(http://blog.csdn.net/livelylittlefish)贴出作者(三二一@小鱼)相关研究、学习内容所做的笔记,欢迎广大朋友指正!
extern "C" 详解
- 在C++中,为了支持重载机制,在编译生成汇编代码时,函数的名字要加入函数的参数类型或者返回值类型等信息
- 在C中,因没有重载机制,编译后的代码只是简单的函数名字而已,不加入其他的信息
1. 不加入extern "C"
testexternc.cpp
int mytest(void) { int a=10,b=20; int c=a+b; return c; } |
Command: g++ -S testexternc.cpp或者gcc -S testexternc.cpp
Generated file: testexternc.s
.file "testexternc.cpp" .text .align 2 .globl _Z6mytestv .type _Z6mytestv, @function _Z6mytestv: .LFB2: pushl %ebp .LCFI0: movl %esp, %ebp .LCFI1: subl $16, %esp .LCFI2: movl $10, -12(%ebp) movl $20, -8(%ebp) movl -8(%ebp), %eax addl -12(%ebp), %eax movl %eax, -4(%ebp) movl -4(%ebp), %eax leave ret .LFE2: .size _Z6mytestv, .-_Z6mytestv .globl __gxx_personality_v0 .ident "GCC: (GNU) 4.1.2 20070925 (Red Hat 4.1.2-33)" .section .note.GNU-stack,"",@progbits |
2. 加入extern "C"
testexternc.cpp
#ifdef __cplusplus extern "C" { #endif
int mytest(void) { int a=10,b=20; int c=a+b; return c; }
#ifdef __cplusplus } #endif | extern "C" int mytest(void) { int a=10,b=20; int c=a+b; return c; } | extern "C" { int mytest(void) { int a=10,b=20; int c=a+b; return c; } } |
三者是等价的。
__cplusplus是cpp中的自定义宏,表示这是一段c++代码。以上黄色部分的内容表示,如果定义了__cplusplus,则用extern "C"{和}处理其中的代码。
Command: g++ -S testexternc.cpp
Generated file: testexternc.s
.file "testexternc.cpp" .text .align 2 .globl mytest .type mytest, @function mytest: .LFB2: pushl %ebp .LCFI0: movl %esp, %ebp .LCFI1: subl $16, %esp .LCFI2: movl $10, -12(%ebp) movl $20, -8(%ebp) movl -8(%ebp), %eax addl -12(%ebp), %eax movl %eax, -4(%ebp) movl -4(%ebp), %eax leave ret .LFE2: .size mytest, .-mytest .globl __gxx_personality_v0 .ident "GCC: (GNU) 4.1.2 20070925 (Red Hat 4.1.2-33)" .section .note.GNU-stack,"",@progbits |
编译生成的代码基本相同,唯独产生的函数名不同。加入extern "C"生成的函数名为mytest,即源代码定义的函数名本身;不加入extern "C"生成的函数名为_Z6mytestv。
3. 带参数的函数汇编代码
还可以看看如下代码不加入extern "C"生成的汇编代码。
testexternc.cpp
int mytest(int a,int b) { int c=a+b; return c; } |
生成的汇编代码如下:
.file "testexternc.cpp" .text .align 2 .globl _Z6mytestii .type _Z6mytestii, @function _Z6mytestii: .LFB2: pushl %ebp .LCFI0: movl %esp, %ebp .LCFI1: subl $16, %esp .LCFI2: movl 12(%ebp), %eax addl 8(%ebp), %eax movl %eax, -4(%ebp) movl -4(%ebp), %eax leave ret .LFE2: .size _Z6mytestii, .-_Z6mytestii .globl __gxx_personality_v0 .ident "GCC: (GNU) 4.1.2 20070925 (Red Hat 4.1.2-33)" .section .note.GNU-stack,"",@progbits |
可以看出,生成的函数名在_Z6mytest后面加上了表示函数参数类型int的i两个。
4. 在3的基础上加入extern "C"
testexternc.cpp
extern "C" int mytest(int a,int b) { int c=a+b; return c; } |
.file "testexternc.cpp" .text .align 2 .globl mytest .type mytest, @function mytest: .LFB2: pushl %ebp .LCFI0: movl %esp, %ebp .LCFI1: subl $16, %esp .LCFI2: movl 12(%ebp), %eax addl 8(%ebp), %eax movl %eax, -4(%ebp) movl -4(%ebp), %eax leave ret .LFE2: .size mytest, .-mytest .globl __gxx_personality_v0 .ident "GCC: (GNU) 4.1.2 20070925 (Red Hat 4.1.2-33)" .section .note.GNU-stack,"",@progbits |
此时按照C规则生成函数名。
5. 若将3的文件改为.c
.file "testexternc.c" .text .globl mytest .type mytest, @function mytest: pushl %ebp movl %esp, %ebp subl $16, %esp movl 12(%ebp), %eax addl 8(%ebp), %eax movl %eax, -4(%ebp) movl -4(%ebp), %eax leave ret .size mytest, .-mytest .ident "GCC: (GNU) 4.1.2 20070925 (Red Hat 4.1.2-33)" .section .note.GNU-stack,"",@progbits |
可以看出生成的函数名与3相同,汇编代码的逻辑也一样,不同的是少了C++汇编后的LFB2、LCFI0~LCFI2、LFE2等符号。
6. 为什么要使用extern "C"?
回答这个问题,我们先看一个例子。
设计一个实际应用:
我们有一个用C语言写好的两个整数加法的库,包括add.h和add.c两个文件;但现在要在C++的程序(或者.cpp文件)中使用这个加法库。
先不考虑extern "C",我们看看这个程序编译和链接过程。
add.h文件
int addition(int a,int b); |
add.c文件
#include "add.h"
int addition(int a,int b) { int c=a+b; return c; } |
main.cpp文件
#include "stdio.h" #include "add.h" extern int addition(int a,int b);
int main() { int a,b,c;
printf("input a:"); scanf("%d",&a); printf("input b:"); scanf("%d",&b);
c=addition(a,b); printf("the result is %d/n",c);
return 0; } |
对add.c和main.cpp的编译均能通过,但在链接main.o和add.o时出现错误。
[root@yu28 testexternc4]# gcc -o add.o -c add.c [root@yu28 testexternc4]# gcc -o main.o -c main.cpp [root@yu28 testexternc4]# gcc -lstdc++ -o main main.o add.o main.o: In function `main': main.cpp:(.text+0x5d): undefined reference to `addition(int, int)' collect2: ld returned 1 exit status [root@yu28 testexternc4]# |
使用gcc链接C++程序时需要加上-lstdc++选项。gcc根据源文件的后缀来确定代码所使用的语言以及默认要链接的库。
在编译main.cpp时编译器是按照C++的方式来处理addition函数的,但实际上链接的库文件却是用C的方式来处理该函数的,因此编译器找不到addition函数,出现"undefined reference to 'addition(intuit)'"的错误。
因此,为了在C++程序中使用用C写成的库,或在C++中尽可能的支持C,使用已经存在的C库,就需要用extern "C"来告诉编译器这是一个用C写成的库文件,并用C的方式来链接它们。
对main.cpp修改如下:
#include "stdio.h"
extern "C" { #include "add.h" extern int addition(int a,int b); }
int main() { int a,b,c; a=100; b=200; c=addition(a,b); printf("the result is %d/n",c);
return 0; } |
编译、链接均能成功。
[root@yu28 testexternc4]# gcc -o add.o -c add.c [root@yu28 testexternc4]# gcc -o main.o -c main.cpp [root@yu28 testexternc4]# g++ main.o add.o -o main [root@yu28 testexternc4]# ls add.c add.h add.o main main.cpp main.o [root@yu28 testexternc4]# ./main input a:100 input b:200 the result is 300 [root@yu28 testexternc4]# |
7. 小结
- 在C++中,为了支持重载机制,在编译生成汇编代码时,函数的名字要加入函数的参数类型或返回值类型等信息。
- 在C中,因没有重载机制,编译后的代码只是简单的函数名字而已,不加入其他的信息。
- 在C++程序中使用用C语言的代码,在编译时编译器是按照C++的方式来处理C的函数的,但实际上链接的库文件却是用C的方式来处理该函数的,因此编译器不会找到C语言的函数,从而出现"undefined reference to 'addition(intuit)'"的错误。
- 因此,为了在C++程序中使用用C写成的库,或在C++中尽可能的支持C,使用已经存在的C库,就需要用extern "C"来告诉编译器这是一个用C写成的库文件,并用C的方式来链接它们。
- gcc根据源文件的后缀来确定代码所使用的语言以及默认要链接的库。
- 使用gcc链接C++程序时需要加上-lstdc++选项。