extern C在C++中到底有什么用?

99ANYc3cd6
预计阅读时长 16 分钟
位置: 首页 C语言 正文

核心概念:一句话总结

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

extern 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 编译器对函数名不做任何修饰,函数名在目标文件中保持原样。

extern c 语言
(图片来源网络,侵删)

void foo(int, double); 在 C 的目标文件中,其符号就是 foo

  • 目的:简单直接。
  • 副作用:无法直接与 C++ 的重载函数对应。

冲突的产生

当你尝试在 C++ 代码中调用一个 C 语言库(一个用 C 编写的 .c 文件编译成的 .lib.a 文件)时,问题就出现了:

  1. C++ 代码调用 foo(1, 2.0)
  2. C++ 编译器会去查找修饰后的符号,_Z3fooid
  3. 但 C 语言库中只提供了未修饰的符号 foo
  4. 链接器找不到 _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

extern 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_libraryanother_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 __cplusplusextern "C" { ... } 结构,使其能被 C++ 正确包含和使用。
常见错误 忘记在 C++ 代码中为 C 函数添加 extern "C" 声明,导致链接时找不到符号。

extern "C" 是 C++ 为了与庞大的 C 生态系统兼容而提供的一个关键桥梁,只要你在 C++ 和 C 之间进行交互,就几乎一定会遇到它。

-- 展开阅读全文 --
头像
dede搜索结果分页如何实现?
« 上一篇 04-20
c语言doAccess是什么?
下一篇 » 04-20

相关文章

取消
微信二维码
支付宝二维码

目录[+]