跳到主要内容

C++ extern "C"

在混合编程的世界中,C++ 和 C 经常需要协同工作。尽管 C++ 是 C 的超集,但由于 C++ 增加了很多新特性(比如名称修饰/名称粉碎机制),直接在 C++ 程序中调用 C 函数或在 C 程序中调用 C++ 函数可能会遇到困难。这时,extern "C" 就成为了连接这两种语言的重要桥梁。

什么是 extern "C"?

extern "C" 是 C++ 中的一个链接规范(linkage specification),它告诉 C++ 编译器按照 C 语言的方式来处理函数名和变量名,即不进行 C++ 特有的名称修饰(name mangling)。

名称修饰(Name Mangling)

C++ 支持函数重载,编译器通过修改函数名(在内部添加额外信息,如参数类型)来区分同名但参数不同的函数。这个过程称为名称修饰或名称粉碎。C 语言不支持函数重载,因此不需要这种机制。

为什么需要 extern "C"?

当你想要在 C++ 代码中调用 C 语言编写的函数,或者希望你的 C++ 函数能被 C 代码调用时,就需要使用 extern "C" 声明。这是因为:

  1. 不同的名称修饰机制:C++ 编译器会对函数名进行修饰,而 C 编译器不会。
  2. 链接兼容性:没有 extern "C",链接器将无法正确匹配函数名。
  3. 跨语言互操作:使现有的 C 代码库能够与 C++ 代码协同工作。

如何使用 extern "C"

单个函数声明

cpp
extern "C" void my_c_function(int arg);

多个函数声明

cpp
extern "C" {
void function1(int arg);
int function2(double arg);
char* function3(void);
}

在头文件中的常见用法

cpp
#ifdef __cplusplus
extern "C" {
#endif

// C 函数声明
void c_function1(void);
int c_function2(int arg);

#ifdef __cplusplus
}
#endif

这种写法确保了当头文件被 C++ 编译器包含时,函数声明会被 extern "C" 包围;而当被 C 编译器包含时,extern "C" 部分会被忽略。

实际例子

示例1:C++ 调用 C 函数

假设我们有一个 C 语言编写的数学库:

math_lib.h

c
#ifndef MATH_LIB_H
#define MATH_LIB_H

#ifdef __cplusplus
extern "C" {
#endif

double add(double a, double b);
double subtract(double a, double b);
double multiply(double a, double b);
double divide(double a, double b);

#ifdef __cplusplus
}
#endif

#endif // MATH_LIB_H

math_lib.c

c
#include "math_lib.h"

double add(double a, double b) {
return a + b;
}

double subtract(double a, double b) {
return a - b;
}

double multiply(double a, double b) {
return a * b;
}

double divide(double a, double b) {
return a / b;
}

然后,我们可以在 C++ 程序中使用这些函数:

main.cpp

cpp
#include <iostream>
#include "math_lib.h"

int main() {
double a = 10.5;
double b = 5.2;

std::cout << "加法: " << add(a, b) << std::endl;
std::cout << "减法: " << subtract(a, b) << std::endl;
std::cout << "乘法: " << multiply(a, b) << std::endl;
std::cout << "除法: " << divide(a, b) << std::endl;

return 0;
}

输出:

加法: 15.7
减法: 5.3
乘法: 54.6
除法: 2.01923

示例2:C 调用 C++ 函数

如果我们想让 C 代码调用 C++ 函数,我们需要将 C++ 函数用 extern "C" 声明:

cpp_functions.h

cpp
#ifndef CPP_FUNCTIONS_H
#define CPP_FUNCTIONS_H

#ifdef __cplusplus
extern "C" {
#endif

void print_message(const char* msg);
int get_length(const char* str);

#ifdef __cplusplus
}
#endif

#endif // CPP_FUNCTIONS_H

cpp_functions.cpp

cpp
#include "cpp_functions.h"
#include <iostream>
#include <cstring>

// 即使是 C++ 实现,由于有 extern "C",这些函数可以被 C 代码调用
extern "C" void print_message(const char* msg) {
std::cout << "消息: " << msg << std::endl;
}

extern "C" int get_length(const char* str) {
return strlen(str);
}

c_main.c

c
#include <stdio.h>
#include "cpp_functions.h"

int main() {
print_message("Hello from C!");

const char* text = "C调用C++函数";
int length = get_length(text);

printf("文本长度: %d\n", length);

return 0;
}

输出:

消息: Hello from C!
文本长度: 17

名称修饰的实际演示

为了更好地理解 extern "C" 的作用,我们可以看看编译器是如何修饰函数名的:

cpp
// 没有使用 extern "C"
void function(int a) { }
void function(double a) { }

// 使用 extern "C"
extern "C" void c_function(int a) { }

如果我们使用工具查看编译后的符号名(比如使用 nm 命令),可能会看到类似以下的输出:

// C++ 修饰后的名称(可能因编译器而异)
_Z8functioni // function(int)
_Z8functiond // function(double)

// C 风格的名称
c_function // extern "C" void c_function(int)

注意事项

  1. 不能用于函数重载:由于 C 不支持函数重载,使用 extern "C" 的函数不能被重载。
cpp
extern "C" void func(int);    // 正确
extern "C" void func(double); // 错误! 同一个 extern "C" 命名空间不能有同名函数
  1. 类成员函数:不能直接将类的成员函数声明为 extern "C",因为成员函数隐含了 this 指针。
cpp
class MyClass {
extern "C" void method(); // 错误! 不能应用于类成员函数
};
  1. 全局变量extern "C" 不仅可以应用于函数,还可以应用于全局变量。
cpp
extern "C" int global_var; // 使用 C 链接规范的全局变量
  1. C++ 特性限制:使用 extern "C" 声明的函数内部仍然可以使用 C++ 特性,但接口必须与 C 兼容。

总结

extern "C" 是 C++ 与 C 语言互操作的重要工具,它通过禁用 C++ 的名称修饰机制,使得 C++ 代码可以调用 C 函数,或者 C 代码可以调用 C++ 函数。在以下情况下,你可能需要使用 extern "C"

  • 在 C++ 代码中调用 C 语言库函数
  • 创建可以被 C 代码调用的 C++ 库
  • 使用需要 C 接口的系统 API

掌握 extern "C" 的使用将帮助你更好地处理混合语言编程的场景,充分利用两种语言的优势。

练习

  1. 创建一个 C++ 类,然后编写一些带有 extern "C" 的包装函数,使 C 代码可以使用该类的功能。

  2. 尝试使用 nm 或类似工具查看编译后的函数名,比较使用 extern "C" 前后的区别。

  3. 实现一个简单的 C 语言库,然后在 C++ 程序中调用它。

相关资源

  • C++ 标准库中的 <cstring>, <cstdio> 等头文件就是 C++ 调用 C 标准库的例子
  • 许多图形库(如 OpenGL)和操作系统 API 都是使用 C 接口设计的,需要在 C++ 中使用 extern "C" 进行调用