核心概念:一句话总结
extern "C" 是一个 C++ 语言中的链接指令,它告诉 C++ 编译器:“接下来的代码,请使用 C 语言的规则来进行编译和链接”,,而不是 C++ 的规则。

为什么需要 extern "C"?
这背后是 C 和 C++ 两个语言的名字修饰(Name Mangling)机制不同导致的。
C++ 的名字修饰
C++ 是一门支持函数重载的语言,为了在同一个程序中区分同名但参数列表不同的函数(void print(int) 和 void print(const char*)),C++ 编译器在编译时会对函数名进行“修饰”,将其转换成一个独一无二、包含函数名、参数类型、返回值类型等信息的内部符号。
一个函数 void foo(int, double); 可能会被修饰成类似 _Z3fooid 这样的名字(具体修饰规则因编译器而异)。
- 目的:支持函数重载。
- 副作用:C++ 的函数名在目标文件中是“面目全非”的。
C 的名字修饰
C 语言不支持函数重载,C 编译器对函数名不做任何修饰,函数名在目标文件中保持原样。

void foo(int, double); 在 C 的目标文件中,其符号就是 foo。
- 目的:简单直接。
- 副作用:无法直接与 C++ 的重载函数对应。
冲突的产生
当你尝试在 C++ 代码中调用一个 C 语言库(一个用 C 编写的 .c 文件编译成的 .lib 或 .a 文件)时,问题就出现了:
- C++ 代码调用
foo(1, 2.0)。 - C++ 编译器会去查找修饰后的符号,
_Z3fooid。 - 但 C 语言库中只提供了未修饰的符号
foo。 - 链接器找不到
_Z3fooid,于是报错:undefined reference to 'foo'。
extern "C" 的作用就是解决这个链接问题。 它告诉 C++ 编译器:“请停止对这段代码进行 C++ 风格的名字修饰,直接使用 C 风格的、未修饰的名字。” 这样,C++ 代码就能正确地找到 C 库中的符号了。
extern "C" 的常见用法
用法 1:在 C++ 中调用 C 函数(最常见)
假设你有一个 C 头文件 c_library.h 和一个 C 源文件 c_library.c。

c_library.h (C 头文件)
// 为了防止 C++ 编译器在包含此头文件时进行错误处理
#ifdef __cplusplus
extern "C" {
#endif
void c_function_from_library(int value);
int another_c_function(int a, int b);
#ifdef __cplusplus
}
#endif
c_library.c (C 源文件)
#include "c_library.h"
void c_function_from_library(int value) {
printf("Called C function with value: %d\n", value);
}
int another_c_function(int a, int b) {
return a + b;
}
解释:
#ifdef __cplusplus:这是一个宏,当被 C++ 编译器包含时,其值为真。extern "C" { ... }:当 C++ 编译器处理这个头文件时,它会用extern "C"将c_function_from_library和another_c_function包裹起来,这意味着,当 C++ 代码包含这个头文件并声明这些函数时,C++ 编译器会为它们生成 C 风格的链接符号。- 这样,C++ 代码就可以像调用普通函数一样调用它们,链接器也能正确解析。
main.cpp (C++ 源文件)
#include <iostream>
#include "c_library.h" // 包含 C 头文件
int main() {
c_function_from_library(42);
int result = another_c_function(10, 20);
std::cout << "Result from C function: " << result << std::endl;
return 0;
}
编译时,你需要同时编译 C 和 C++ 文件:
# 1. 编译 C 文件,生成 c_library.o gcc -c c_library.c -o c_library.o # 2. 编译 C++ 文件,生成 main.o g++ -c main.cpp -o main.o # 3. 链接 C 和 C++ 目标文件,生成最终可执行文件 g++ main.o c_library.o -o my_program
用法 2:在 C 中使用 C++ 代码(较少见)
这种情况比较少见,因为 C++ 依赖 C++ 标准库,很难在纯 C 环境中编译,但如果需要,原理是相同的。
cpp_library.h (C++ 头文件)
#ifdef __cplusplus // 如果是被 C++ 编译器包含,正常声明 void cpp_function(); #else // 如果是被 C 编译器包含,用 extern "C" 声明 // 注意:C 编译器不认识 extern "C",所以用宏保护 extern void cpp_function(); #endif
cpp_library.cpp (C++ 源文件)
#include "cpp_library.h"
#include <iostream>
void cpp_function() {
std::cout << "This is a C++ function." << std::endl;
}
main.c (C 源文件)
// C 编译器不认识 extern "C",所以需要从 .cpp 文件中提取出 C 风格的声明
// 或者使用上面的 cpp_library.h 中的 extern 声明
extern void cpp_function(); // 告诉 C 链接器,这个函数存在
int main() {
cpp_function();
return 0;
}
编译命令:
# 1. 编译 C++ 文件,生成 cpp_library.o g++ -c cpp_library.cpp -o cpp_library.o # 2. 编译 C 文件,生成 main.o gcc -c main.c -o main.o # 3. 链接 # 注意:链接时可能需要链接 C++ 标准库 g++ main.o cpp_library.o -o my_program -lstdc++
extern "C" 的高级用法
作用于单个函数/变量
extern "C" void a_single_c_function(); extern "C" int some_c_variable;
作用于一个代码块
extern "C" {
void c_func1();
void c_func2();
int c_var;
}
作用于整个头文件(推荐)
这是最清晰、最常用的方式,如前面 c_library.h 的例子所示,它能确保整个头文件中的所有 C 兼容声明都受到 extern "C" 的保护。
条件编译 if/else
你也可以显式地写出 if/else 结构,虽然不常见,但更清晰。
#ifdef __cplusplus
extern "C" {
#endif
// C 代码声明
void c_func();
#ifdef __cplusplus
}
#endif
C++11 及以后:extern "C" { ... } 内部也可以声明 C++ 函数
从 C++11 开始,标准允许在 extern "C" 代码块中声明 C++ 函数,这主要是为了在 C++ 的头文件中,方便地组织一些需要 C 链接的函数(用于与 C 交互的接口函数)。
extern "C" {
// 这个函数是 C++ 函数,但拥有 C 链接
// 它可以被 C 代码调用(前提是 C++ 编译器生成了对应的 C 风格符号)
void cpp_function_with_c_linking() {
// ... 可以使用 C++ 特性 ...
}
}
| 特性 | 描述 |
|---|---|
| 核心作用 | 链接指令,控制 C++ 编译器如何生成函数/变量的符号名。 |
| 解决的问题 | C++ 的名字修饰与 C 的简单符号名之间的冲突,使得 C++ 和 C 代码可以互相调用。 |
| 主要场景 | C++ 调用 C 库:最常见的情况。 |
| C 调用 C++ 库:较少见,需要额外处理 C++ 标准库。 | |
| 最佳实践 | 在 C 语言头文件中使用 #ifdef __cplusplus 和 extern "C" { ... } 结构,使其能被 C++ 正确包含和使用。 |
| 常见错误 | 忘记在 C++ 代码中为 C 函数添加 extern "C" 声明,导致链接时找不到符号。 |
extern "C" 是 C++ 为了与庞大的 C 生态系统兼容而提供的一个关键桥梁,只要你在 C++ 和 C 之间进行交互,就几乎一定会遇到它。
