14 / 80 · C++11 · 约 9 分钟
extern "C":语言链接与 C ABI 边界
extern "C" 为适用的函数和变量声明指定 C 语言链接,用于连接兼容的 C 接口。它不是把函数体切换为 C 语言,也不让任意 C++ 类型获得跨语言 ABI;名字、调用约定、布局和资源责任必须分别处理。
语言链接不只是去掉名字修饰
C++ 编译器通常会把参数类型等信息编码进外部符号名,以支持重载。extern "C" 指定 C 语言链接,便于同兼容 C 工具链生成的代码连接;标准层面语言链接还关系到函数类型和调用约定,并不规定统一的跨平台符号拼写。
它应出现在命名空间作用域。同一实体的声明必须保持一致的语言链接,不能先按普通 C++ 函数声明,随后把同一个函数重新说成 C 链接。定义源文件应包含接口头文件,从编译时就检查双方声明匹配。
函数体仍然是 C++
一个 C 链接函数的实现仍可使用 C++ 类、模板和 RAII,它仍由 C++ 编译器按 C++ 规则编译。示例的接口仅使用 int,函数体调用一个 C++ 类的静态成员函数,说明链接规范没有改变函数体的语言。
把 class 放进 extern C 大括号也不会使其成员函数变成普通 C 接口。C 端不理解引用、模板、重载集合或 std::string 的库布局;应导出小而明确的自由函数,必要时用不透明句柄把 C++ 对象隐藏在实现侧。
稳定边界还需要协议
供 C 与 C++ 共用的头文件通常用 #ifdef __cplusplus 包住 extern C 声明区,避免 C 编译器遇到不认识的语法。接口除了参数类型,还应说明指针是否可空、缓冲区长度、错误码,以及由哪一侧分配和释放资源。
C++ 异常不应越过不理解异常的 C 边界,包装层应捕获并转换为约定错误。相同 C 链接声明也不保证不同架构、编译器选项或运行库天然二进制兼容;实际 ABI 仍需目标工具链约定,跨进程或跨机器通信则应使用序列化协议。
容易答错的地方
- 不能靠两个同名 C 链接函数的不同参数类型建立普通 C++ 重载集合;为 C 导出接口使用不同名字。
- extern "C" 不规定所有权、不做编码转换,也不会自动阻止异常从实现中逸出。
运行一个例子
最低标准 C++11 · 完整程序 · 下载 .cpp
#include <cassert>
#include <iostream>
extern "C" int guide_double_small(int value);
class Calculator {
public:
static int double_small(int value) {
return (value >= 0 && value <= 100) ? value * 2 : -1;
}
};
extern "C" int guide_double_small(int value) {
return Calculator::double_small(value);
}
int main() {
assert(guide_double_small(4) == 8);
assert(guide_double_small(101) == -1);
std::cout << guide_double_small(4) << '\n';
}
在本地编译
g++ -std=c++11 -Wall -Wextra -Wpedantic -pthread basics-extern-c.cpp -o example && ./example预期结果
8
CHECK YOUR UNDERSTANDING
合上答案,试着解释。
准备向 C 导出返回 std::string 的函数,仅添加 extern "C" 足够吗?提出一个更合适的边界。
查看参考答案
不够,C 无法理解 std::string 的构造、析构和 ABI。可以导出接受调用者缓冲区及容量的函数,用返回值报告所需长度或错误码,明确是否包含末尾零字符。C++ 包装层捕获异常并映射为错误结果,避免跨边界转交标准库对象及其释放责任。
继续查证
标准草案链接会随工作草案更新;本文版本标记对应示例最低要求,不表示草案中的所有新规则都适用于旧标准。