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++选项。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/livelylittlefish/archive/2009/08/22/4471591.aspx