extern "c" 是用于解决 c++ 与 c 混合链接时符号不匹配的问题,它禁用 c++ 名字修饰(name mangling),使函数按 c 的链接规范处理;常用于包裹 c 头文件声明或导出 c++ 函数供 c 调用,但不可用于模板、内联函数或类成员函数。

extern "C" 是用来解决 C++ 和 C 混合链接的问题
它不是让 C++ 代码“变成 C”,而是告诉 C++ 编译器:这部分声明/定义,按 C 的符号命名规则(即不进行 name mangling)来处理。常见于头文件中包裹 C 库函数声明,或导出供 C 代码调用的 C++ 函数。
在头文件里用 extern "C" 包裹 C 函数声明
如果你要 #include 一个纯 C 头文件(比如 some_c_lib.h),而这个头文件没自带 extern "C" 保护,直接在 C++ 里包含会导致链接失败——因为 C++ 编译器对函数名做了 mangling,而 C 链接器找不到对应符号。
正确做法是手动加一层包裹:
#ifdef __cplusplus
extern "C" {
#endif
<h1>include "some_c_lib.h"</h1><h1>ifdef __cplusplus</h1><p>}</p><h1>endif</h1>
注意:__cplusplus 是 C++ 编译器预定义宏,C 编译器没有它,所以这段不会影响纯 C 编译。
- 不要只在外层加
extern "C"而漏掉#ifdef—— 否则 C 编译器会报错,不认识extern "C" - 不要把整个头文件内容复制进来再包裹——容易遗漏更新,应直接包裹
#include行 - 如果该 C 头文件自己已带
extern "C"宏保护(常见于成熟库),就不用重复加
用 extern "C" 导出 C++ 函数供 C 代码调用
你想写一个 C++ 函数(比如 process_data()),但希望 C 代码能调用它。这时必须用 extern "C" 声明函数接口,且定义也必须匹配——否则链接时 C 代码找不到符号。
典型写法:
extern "C" {
int process_data(const char* input, int len);
}
<p>// 定义必须和声明一致(不能在 .cpp 里再写 extern "C" 包裹,除非显式指定 linkage)
int process_data(const char* input, int len) {
// 实现...
return 0;
}</p>
关键点:
-
extern "C"只作用于函数声明(或变量声明),不影响函数体内实现逻辑 - 函数参数类型必须是 C 兼容的:避免
std::string、std::vector、引用、重载、类成员函数等 - 返回类型同理:不能返回 C++ 类型;若需传递复杂结构,用
struct+ 指针,且确保内存布局可被 C 代码理解 - 如果定义和声明分离,声明必须在
extern "C"块内;定义所在文件无需额外修饰(但必须与声明签名完全一致)
extern "C" 不能用于模板、内联函数或类成员函数
这些特性依赖 C++ 的 name mangling 或语义机制,extern "C" 会直接导致编译失败或未定义行为。
例如以下写法是错的:
extern "C" template<typename t> void foo(T x); // ❌ 编译错误
extern "C" inline int bar() { return 42; } // ❌ 大多数编译器拒接
extern "C" void MyClass::method(); // ❌ 成员函数隐含 this 指针,C 无法调用</typename>
替代方案:
- 模板 → 提前实例化为具体类型,再用
extern "C"导出对应函数(如void foo_int(int)) - 内联函数 → 去掉
inline,或仅在头文件中提供完整定义(但不能再用extern "C") - 类成员函数 → 提供 C 风格包装函数,用
void*传对象指针,内部做 static_cast
C++ 和 C 混合时,符号可见性是隐性门槛。很多人卡在链接阶段才意识到问题,但真正要查的是头文件是否被正确包裹、函数签名是否真的 C 兼容——而不是反复检查 Makefile 或链接顺序。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











