本文是“攻玉计划”的一部分,翻译自 https://stackoverflow.com/questions/1041866/what-is-the-effect-of-extern-c-in-c 中 Ciro Santilli 的回答

通过反汇编了解 extern “C” 的作用

main.cpp

1
2
3
4
5
6
7
8
9
10
void f() {}
void g();

extern "C" {
void ef() {}
void eg();
}

/* Prevent g and eg from being optimized away. */
void h() { g(); eg(); }

将上述代码编译为 ELF 格式的二进制,然后反汇编:

1
2
g++ -c -std=c++11 -Wall -Wextra -pedantic -o main.o main.cpp
readelf -s main.o

摘取一部分输出:

1
2
3
4
5
6
 8: 0000000000000000     7 FUNC    GLOBAL DEFAULT    1 _Z1fv
9: 0000000000000007 7 FUNC GLOBAL DEFAULT 1 ef
10: 000000000000000e 17 FUNC GLOBAL DEFAULT 1 _Z1hv
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _Z1gv
13: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND eg

可见:

  • efeg 在符号表中的名称与其在原先代码中的名称一致
  • 其他符号都被修饰过了,我们可以用 c++filt 工具还原其本来的样子:
1
2
3
4
5
6
$ c++filt _Z1fv
f()
$ c++filt _Z1hv
h()
$ c++filt _Z1gv
g()

所以,在以下两种情况下,我们需要使用 extern "C"

  • 在 C++ 中调用 C 代码:告诉 g++ 可能会遇到由 gcc 生成的未修饰过的符号名
  • 在 C 中调用 C++ 代码:让 g++ 生成未修饰的符号名,以供 gcc 调用

某些不能在 extern “C” 中使用的代码

显然,任何需要使用 C++ 名称修饰的语言特性,都不能写在 extern "C" 中:

1
2
3
4
5
6
7
8
9
10
extern "C" {
// 函数重载
// 报错:f 的声明有冲突
void f();
void f(int i);

// 模板
// 报错:模板不能用于 C 的链接
template <class C> void f(C i) { }
}

在 C++ 中调用 C 代码的最小可运行代码样例

在 C++ 中调用 C 代码很简单:每个 C 函数都只有一个未修饰的符号名,所以不需要额外的操作。

main.cpp

1
2
3
4
5
6
7
#include <cassert>

#include "c.h"

int main() {
assert(f() == 1);
}

c.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#ifndef C_H
#define C_H

/* 这里,ifdef 可以让这个头文件既可以用于 C++ 工程,也能用于 C 工程,
* 因为 C 标准里面没有 extern "C" 的定义 */
#ifdef __cplusplus
extern "C" {
#endif
int f();
#ifdef __cplusplus
}
#endif

#endif

c.c

1
2
3
#include "c.h"

int f(void) { return 1; }

运行:

1
2
3
4
g++ -c -o main.o -std=c++98 main.cpp
gcc -c -o c.o -std=c89 c.c
g++ -o main.out main.o c.o
./main.out

如果没有 extern "C" 的话,链接器会报错:

1
main.cpp:6: undefined reference to `f()'

因为 g++ 会寻找一个修饰过的 f,但是 gcc 并不会编译出修饰过的符号名。

在 C 中调用 C++ 代码的最小可运行代码样例

在 C 中调用 C++ 代码稍微困难一点:我们需要手动管理所有暴露给 C 的函数接口,并且使它们在编译时不被修饰。

代码如下:

main.c

1
2
3
4
5
6
7
8
9
#include <assert.h>

#include "cpp.h"

int main(void) {
assert(f_int(1) == 2);
assert(f_float(1.0) == 3);
return 0;
}

cpp.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef CPP_H
#define CPP_H

#ifdef __cplusplus
// 这两个重载的函数不能暴露给 C 的编译群,否则会报错
int f(int i);
int f(float i);
extern "C" {
#endif
int f_int(int i);
int f_float(float i);
#ifdef __cplusplus
}
#endif

#endif

cpp.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "cpp.h"

int f(int i) {
return i + 1;
}

int f(float i) {
return i + 2;
}

int f_int(int i) {
return f(i);
}

int f_float(float i) {
return f(i);
}

运行:

1
2
3
4
gcc -c -o main.o -std=c89 -Wextra main.c
g++ -c -o cpp.o -std=c++98 cpp.cpp
g++ -o main.out main.o cpp.o
./main.out

如果不加 extern "C" 的话,会报错:

1
2
main.c:6: undefined reference to `f_int'
main.c:7: undefined reference to `f_float'

因为 g++ 会生成修饰后的符号名,但 gcc 无法理解。

当我在 C++ 中包含标准库 C 头文件的时候,extern “C” 在哪?