简单来说,extern “C”是C++声明或定义C语言符号的方法,是为了与C兼容。说来容易,要理解起来还是得费些周折,首先我们要从C++和C的区别说起。
符号
大家都知道,从代码到可执行程序需要经过编译和链接两个过程,其中编译阶段会做语法检测,代码展开,另外它还会做一件事,就是将变量转成符号,链接的时候其实是通过符号来定位的。编译器在编译C和C++代码时,将变量转成符号的过程是不同的。本文所使用的编译器为gcc4.4.7
我们先来看一段简单的代码
/* hello.c */ #include <stdio.h> const char* g_prefix = "hello "; void hello(const char* name) { printf("%s%s", g_prefix, name); }
$ nm hello.o 0000000000000000 D g_prefix 0000000000000000 T hello U printf
0000000000000000 T _Z5helloPKc U __gxx_personality_v0 0000000000000000 D g_prefix U printf
1、 符号以_Z开始
2、 如果有嵌套,后面紧跟N,然后是名称空间、类、函数的名字,名字前的数字是长度,以E结尾
3、 如果没嵌套,则直接是名字长度后面跟着名字
4、 最后是参数列表,类型和符号对应关系如下
int -> i float -> f double -> d char -> c void -> v const -> K * -> P
下面列举几个函数和符号的对应例子
这样也很容易理解为什么C++支持函数重载而C不支持了,因为C++将函数修饰为符号时把函数的参数类型加进去了,而C却没有,所以在C++下,即便函数名相同,只要参数不同,它们的符号名是不会冲突的。我们可以通过下面一个例子来验证变量名和符号的这种关系。
/ * filename : test.cpp */ #include <stdio.h> namespace myname { int var = 42; } extern int _ZN6myname3varE; int main() { printf("%d\n", _ZN6myname3varE); return 0; }
这里我们在名称空间namespace定义了全局变量var,根据前面的内容,它会被修饰为符号_ZN6myname3varE,然后我们手动声明了外部变量_ZN6myname3varE并将其打印出来。编译并运行,它的值正好就是var的值
$ gcc test.cpp -o test -lstdc++ $ ./test 42
extern "C"
有了符号的概念我们再来看extern “C”的用法就很容易了
extern "C" { int func(int); int var; }
extern "C" int func(int); extern "C" int var;
这样就声明了C类型的func和var。很多时候我们写一个头文件声明了一些C语言的函数,而这些函数可能被C和C++代码调用,当我们提供给C++代码调用时,需要在头文件里加extern “C”,否则C++编译的时候会找不到符号,而给C代码调用时又不能加extern “C”,因为C是不支持这样的语法的,常见的处理方式是这样的,我们以C的库函数memset为例
#ifdef __cplusplus extern "C" { #endif void *memset(void*, int, size_t); #ifdef __cplusplus } #endif