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)。
C++ 支持函数重载,编译器通过修改函数名(在内部添加额外信息,如参数类型)来区分同名但参数不同的函数。这个过程称为名称修饰或名称粉碎。C 语言不支持函数重载,因此不需要这种机制。
为什么需要 extern "C"?
当你想要在 C++ 代码中调用 C 语言编写的函数,或者希望你的 C++ 函数能被 C 代码调用时,就需要使用 extern "C"
声明。这是因为:
- 不同的名称修饰机制:C++ 编译器会对函数名进行修饰,而 C 编译器不会。
- 链接兼容性:没有
extern "C"
,链接器将无法正确匹配函数名。 - 跨语言互操作:使现有的 C 代码库能够与 C++ 代码协同工作。
如何使用 extern "C"
单个函数声明
extern "C" void my_c_function(int arg);
多个函数声明
extern "C" {
void function1(int arg);
int function2(double arg);
char* function3(void);
}
在头文件中的常见用法
#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
#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
#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
#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
#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
#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
#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"
的作用,我们可以看看编译器是如何修饰函数名的:
// 没有使用 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)
注意事项
- 不能用于函数重载:由于 C 不支持函数重载,使用
extern "C"
的函数不能被重载。
extern "C" void func(int); // 正确
extern "C" void func(double); // 错误! 同一个 extern "C" 命名空间不能有同名函数
- 类成员函数:不能直接将类的成员函数声明为
extern "C"
,因为成员函数隐含了this
指针。
class MyClass {
extern "C" void method(); // 错误! 不能应用于类成员函数
};
- 全局变量:
extern "C"
不仅可以应用于函数,还可以应用于全局变量。
extern "C" int global_var; // 使用 C 链接规范的全局变量
- C++ 特性限制:使用
extern "C"
声明的函数内部仍然可以使用 C++ 特性,但接口必须与 C 兼容。
总结
extern "C"
是 C++ 与 C 语言互操作的重要工具,它通过禁用 C++ 的名称修饰机制,使得 C++ 代码可以调用 C 函数,或者 C 代码可以调用 C++ 函数。在以下情况下,你可能需要使用 extern "C"
:
- 在 C++ 代码中调用 C 语言库函数
- 创建可以被 C 代码调用的 C++ 库
- 使用需要 C 接口的系统 API
掌握 extern "C"
的使用将帮助你更好地处理混合语言编程的场景,充分利用两种语言的优势。
练习
-
创建一个 C++ 类,然后编写一些带有
extern "C"
的包装函数,使 C 代码可以使用该类的功能。 -
尝试使用
nm
或类似工具查看编译后的函数名,比较使用extern "C"
前后的区别。 -
实现一个简单的 C 语言库,然后在 C++ 程序中调用它。
相关资源
- C++ 标准库中的
<cstring>
,<cstdio>
等头文件就是 C++ 调用 C 标准库的例子 - 许多图形库(如 OpenGL)和操作系统 API 都是使用 C 接口设计的,需要在 C++ 中使用
extern "C"
进行调用