extern “C”的主要作用是让C++中的函数名字使用C语言的编译处理方式(也就是在编译阶段,确切来说 是汇编阶段不对函数名进行mangle处理),这样就可以让C语言代码能够通过“与C语言兼容的方式声明的头文件”来链接C++编译器生成的二进制格式文件中函数。由于C++支持函数重载,而C语言不支持,C++编译器为了支持函数重载,就需要为同名但参数列表不同的函数名指定一个全局唯一的名字(该名字被称为mangled name),也就是编译阶段的mangle name过程,这些名字通常会包含相应的参数列表类型等,不同的编译器实现不同。 由于有extern的修饰,表示被修饰的变量或函数的对外部文件可见,或其定义来自其他文件中,与static的作用刚好相反。 SOF上的回答:http://stackoverflow.com/questions/1041866/in-c-source-what-is-the-effect-of-extern-c
下面以代码来分析,环境为ubuntu 14.04.4,gcc 4.8.4: 文件较多,一一分析。文件名:extern_c.h ,该头文件显然是可以被C及C++源码包含的,因为里面没有用到C++的高级特性
1 2 3 4 5 6 7 #ifndef EXTERN_C_H #define EXTERN_C_H int add (int , int ) ;char get (char ) ;int mult (int , int ) ;#endif
文件名:divi.h ,该文件中包含有不兼容于C语言的头文件,所以这个文件是不能被C源码包含的
1 2 3 4 5 6 7 #ifndef DIVI_H #define DIVI_H #include <iostream> float divi (float , float ) ;#endif
文件名:extern_add.cpp,该文件中的add函数被声明为extern “C”属性
1 2 3 4 5 extern "C" int add (int x, int y) { return x + y; }
查看其汇编代码(命令g++ -S extern_add.cpp,汇编文件名为extern_add.s)如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 .file "extern_add.cpp" .text .globl add .type add, @function add: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movl %edi, -4(%rbp) movl %esi, -8(%rbp) movl -8(%rbp), %eax movl -4(%rbp), %edx addl %edx, %eax popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size add, .-add .ident "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4" .section .note.GNU-stack,"",@progbits
显示汇编之后的函数add的名字依然是add
文件名:extern_c.cpp ,该文件是extern_c.h头文件中声明的函数的定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 extern "C" { #include "extern_c.h" } extern "C" { char get (char ch) { return ch; } } int mult (int a, int b) { return a * b; } float divi (float a, float b) { return a / b; }
对extern_c编译之后读取其符号表内容如下(命令readelf -s extern_c.o):
1 2 3 4 5 6 7 8 9 10 11 12 13 Symbol table '.symtab' contains 11 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS extern_c.cpp 2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 3: 0000000000000000 0 SECTION LOCAL DEFAULT 2 4: 0000000000000000 0 SECTION LOCAL DEFAULT 3 5: 0000000000000000 0 SECTION LOCAL DEFAULT 5 6: 0000000000000000 0 SECTION LOCAL DEFAULT 6 7: 0000000000000000 0 SECTION LOCAL DEFAULT 4 8: 0000000000000000 15 FUNC GLOBAL DEFAULT 1 get 9: 000000000000000f 19 FUNC GLOBAL DEFAULT 1 mult 10: 0000000000000022 42 FUNC GLOBAL DEFAULT 1 _Z4diviff
从符号表中可以看到被声明为extern “C”的函数get和mult的函数名在汇编之后没有变,这样当C语言代码的链接这个编译之后的文件时才能找到相应的函数get和mult,但找不到div,因为可以看到div在编译之后名字被mangle成为_Z4diviff。(当然通过查看汇编代码也可以清楚地分析到来其作用,因为mangle是在汇编阶段进行的)
文件名:c_test_extern.c ,该文件为C语言源代码,用它来链接C++编译生成的可链接的目标文件
1 2 3 4 5 6 7 8 9 10 11 12 #include "extern_c.h" #include <stdio.h> int main (void ) { int a = 3 , b = 5 ; int a_b_add = add(a, b); int a_b_mult = mult(a, b); char ch = get(65 ); printf ("a + b = %d\na * b = %d\nchar(65) = %c\n" , a_b_add, a_b_mult, ch); return 0 ; }
编译方法:
1 2 3 g++ -c extern_c.cpp extern_add.cpp #编译C++源文件 gcc -c c_test_extern.c #编译C语言源文件 gcc -o c_test_extern extern_c.o extern_add.o c_test_extern.o #将链接C与C++源文件编译出的可重定位的目标文件,并生成可执行的目标文件c_test_extern
没有任何问题,因为gcc在链接时,由于C++的目标文件在编译时函数名没有被mangle,所以可以正常找到函数名 执行c_test_extern输出为:
1 2 3 a + b = 8 a * b = 15 char(65) = A
假如在定义add函数的C++文件extern_c.cpp中不声明为extern “C”,则链接会出错,错误内容为:
1 2 3 c_test_extern.o: In function `main': c_test_extern.c:(.text+0x21): undefined reference to `add' collect2: error: ld returned 1 exit status
提示很明显:未定义的引用add,因为add的名字被g++ mangle成其他名字了。
文件名:cpp_test_extern.cpp 用于测试C++引用extern “C”中的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 extern "C" {#include "extern_c.h" } #include "divi.h" int main (void ) { int a = 3 , b = 5 ; int a_b_add = add (a, b); int a_b_mult = mult (a, b); float a_b_div = divi (a, b); char ch = get (65 ); std::cout << "a + b = " << a_b_add << std::endl; std::cout << "a * b = " << a_b_mult << std::endl; std::cout << "a / b = " << a_b_div << std::endl; std::cout << "char(65) = " << ch << std::endl; return 0 ; }
编译方法:
1 g++ -o cpp_test_extern cpp_test_extern.cpp extern_c.cpp extern_add.cpp
运行结果为:
1 2 3 4 a + b = 8 a * b = 15 a / b = 0.6 char(65) = A
假如在包含头文件时,不使用extern “C”声明头文件extern_c.h,编译会输出错误:
1 2 3 4 5 cpp_test_extern.o: In function `main': cpp_test_extern.cpp:(.text+0x22): undefined reference to `add(int, int)' cpp_test_extern.cpp:(.text+0x34): undefined reference to `mult(int, int)' cpp_test_extern.cpp:(.text+0x5b): undefined reference to `get(char)' collect2: error: ld returned 1 exit status
错误意思为: 未定义的引用,即无法找到相应的函数
最后注意extern “C”影响的只是编译器对于编译时函数名的处理方式,并不影响语言特性,也就是说不管带不带extern “C”,代码都是以C++的方式处理,而不是说带了extern “C”的代码就以C语言方式处理。例如对于常量字符,我们知道C语言认为是int型,如’a’,C语言认为其占用4个字节,而C++认为是char型,即1个字节。如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <stdio.h> void cpp_print (void ) { char c='a' ; printf ("%d %d\n" ,sizeof (c),sizeof ('a' )); } extern "C" void c_print (void ) { char c='a' ; printf ("extern_c:%d %d\n" ,sizeof (c),sizeof ('a' )); } int main () { cpp_print (); c_print (); return 0 ; }
输出结果为:
显示都是C++的方式处理的