本文是“攻玉计划”的一部分,翻译自 https://stackoverflow.com/questions/1041866/what-is-the-effect-of-extern-c-in-c 中 Ciro Santilli 的回答
通过反汇编了解 extern “C” 的作用
main.cpp
1 | void f() {} |
将上述代码编译为 ELF 格式的二进制,然后反汇编:
1 | g++ -c -std=c++11 -Wall -Wextra -pedantic -o main.o main.cpp |
摘取一部分输出:
1 | 8: 0000000000000000 7 FUNC GLOBAL DEFAULT 1 _Z1fv |
可见:
ef
和eg
在符号表中的名称与其在原先代码中的名称一致- 其他符号都被修饰过了,我们可以用
c++filt
工具还原其本来的样子:
1 | $ c++filt _Z1fv |
所以,在以下两种情况下,我们需要使用 extern "C"
:
- 在 C++ 中调用 C 代码:告诉
g++
可能会遇到由gcc
生成的未修饰过的符号名 - 在 C 中调用 C++ 代码:让
g++
生成未修饰的符号名,以供gcc
调用
某些不能在 extern “C” 中使用的代码
显然,任何需要使用 C++ 名称修饰的语言特性,都不能写在 extern "C"
中:
1 | extern "C" { |
在 C++ 中调用 C 代码的最小可运行代码样例
在 C++ 中调用 C 代码很简单:每个 C 函数都只有一个未修饰的符号名,所以不需要额外的操作。
main.cpp
1 |
|
c.h
1 |
|
c.c
1 |
|
运行:
1 | g++ -c -o main.o -std=c++98 main.cpp |
如果没有 extern "C"
的话,链接器会报错:
1 | main.cpp:6: undefined reference to `f()' |
因为 g++
会寻找一个修饰过的 f
,但是 gcc
并不会编译出修饰过的符号名。
在 C 中调用 C++ 代码的最小可运行代码样例
在 C 中调用 C++ 代码稍微困难一点:我们需要手动管理所有暴露给 C 的函数接口,并且使它们在编译时不被修饰。
代码如下:
main.c
1 |
|
cpp.h
1 |
|
cpp.cpp
1 |
|
运行:
1 | gcc -c -o main.o -std=c89 -Wextra main.c |
如果不加 extern "C"
的话,会报错:
1 | main.c:6: undefined reference to `f_int' |
因为 g++
会生成修饰后的符号名,但 gcc
无法理解。
当我在 C++ 中包含标准库 C 头文件的时候,extern “C” 在哪?
- C++ 版本的 C 头文件,比如
cstdio
可能靠#pragma GCC system_header
这个编译宏实现的,根据 https://gcc.gnu.org/onlinedocs/cpp/System-Headers.html :在某些平台,例如 RS/6000 AIX 上编译 C++ 代码时,GCC 会隐式地将所有系统头文件用 extern “C” 包含起来。但我并不完全确定。 - POSIX 标准的头文件,比如
/usr/include/unistd.h
会使用__BEGIN_DECLS
宏,而__BEGIN_DECLS
会定义为extern "C" {
。详见 https://stackoverflow.com/questions/8087438/do-i-need-an-extern-c-block-to-include-standard-posix-c-headers/8087539#8087539。