直接#include C头文件会链接失败,因C++编译器对函数名进行name mangling而C不修饰,导致链接器找不到原始符号;解决方法是用extern "C"告知C++编译器按C规则处理符号。
因为 C++ 编译器对函数名做 name mangling(名字修饰),而 C 编译器不修饰。比如 C 中的 void init() 在 C++ 目标文件里可能变成 _Z4initv,链接器找不到原始的 init 符号,报 undefined reference to 'init'。
解决方法不是改 C 库,而是告诉 C++ 编译器:“这部分函数按 C 的规则处理符号”。核心就是 extern "C" 块。
写法一:包裹整个头文件(推荐用于你自己控制的 C 头)
#ifdef __cplusplus
extern "C" {
#endif
void process_data(int* buf, size_t len);
int get_version(void);
ifdef __cplusplus
}
endif
写法二:在 C++ 源文件中显式声明(适合无法修改的第三方 C 头)
extern "C" {
#include "legacy_c_lib.h" // 确保该头内部没用 extern "C"
}
// 或者只声明需要的函数(更安全)
extern "C" {
int c_calc_sum(const int*, int);
void c_reset_state();
}
#ifdef __cplusplus 保护,直接 #include 即可class、template),必须用写法二 + 显式声明,避免编译错误
extern "C" 只影响链接符号,不影响调用约定(如 __cdecl / __stdcall),Windows 下需额外确认不能。extern "C" 只作用于函数和全局变量,对类型定义无效。C 和 C++ 对结构体的 ABI 兼容性取决于:
#pragma pack 或 alignas 统一)char*,C++ 用 std::string,必须显式转换安全做法是只传 Plain Old Data(POD)结构,例如:
struct Config {
int timeout_ms;
char server_ip[64];
uint8_t enable_ssl;
}; // 这个结构体在 C/C++ 中二进制布局一致用 dlopen/LoadLibrary 手动加载时,extern "C" 依然必要 —— 它确保你声明的函数指针类型与实际符号匹配。
dlsym 获取函数地址后,必须用 extern "C" 声明的函数指针类型来 cast,否则调用可能崩溃__declspec(dllexport),或 .def 文件未导出 unmangled 名,GetProcAddress 会失败nm -D libxxx.so | grep init,看输出是否为 init
_Z4initv)常见坑:C 库用 static 修饰函数,导致符号不可见,extern "C" 也救不了。