一、extern关键字设计的目的只有一个:那就是跨文件、跨语言地正确找到符号
-
作为外部声明,告诉编译器变量或函数在别的文件里有定义,让编译器去别的目标文件找查看,先别急着报错。
-
配合C语言使用(extern "C"),用于禁止C++的符号修饰(Name Mangling)功能,实现C和C++的混合编程
二、extern的基础使用
修饰变量或函数,只做声明,不定义
-
本质:extern 关键字修饰的声明是承诺,而不是定义,它不分配内存,只是告诉编译器,这个符号(变量/函数)在其他编译单元(.cpp文件)中有定义,请链接时去哪里找。
-
代码实现
cpp
// FileA.cpp
int g_counter = 0; // 定义(分配内存)
void logMessage() { ... } // 定义
// FileB.cpp
extern int g_counter; // 声明(不分配内存),告诉编译器去外部找
extern void logMessage(); // 声明(通常省略,函数默认就是extern的)
void test() {
g_counter++; // 链接时去 FileA.obj 找 g_counter
logMessage(); // 链接时去 FileA.obj 找 logMessage
}
三、extern "C"的底层原理与使用场景
1、为什么需要设计出extern "C"?
-
一句话总结:为了让C++兼容C,实现在C++程序中调用C语言函数库
-
C语言:符号名就是函数名,比如 void foo(int) 他的符号名就是 foo (注意C语言没有函数重载)
-
C++:支持函数重载,编译器会进行名字修饰,void foo(int) 会被修饰为 _Z3fooi, 在修饰后的符号名会标记参数信息
使用冲突:
- 如果C++程序要调用一个C语言的库(.so, .a),默认链接时会去找 _Z3fooi,但C语言的库中只有foo,肯定找不到,会报链接错误(Undefined Reference)
2、extern "C"的标准写法
1)直接包裹单个函数
- 在C++程序中调用C语言函数
cpp
extern "C" {
void c_function(int x); // 告诉编译器:按 C 的规则去找这个函数
int c_global_var;
}
2)兼容C/C++两种语言的双编译的头文件中调用extern
-
既能让C++编译器识别,也能让C编译器识别
-
__cplusplus是C++编译器内置的宏
cpp
// my_module.h
#ifdef __cplusplus
extern "C" {
#endif
// 这里是 C 语言接口声明(结构体、函数原型)
void init_module();
void process_data(int* data, int len);
#ifdef __cplusplus
}
#endif
3、类成员不能使用extern "C"
-
extern "C"只能修饰全局函数和全局变量
-
不能作用域类成员函数,因为成员函数this指针和虚表(vtable),而C语言没有这两个概念