extern 关键字的作用详解

在 C++ 中,extern 关键字的主要作用是用于声明变量或函数是 外部定义的,即它们的定义在另一个文件中。extern 通常用于引入外部符号(变量或函数)以供当前编译单元使用。以下是 extern 关键字的两个主要用途:

  1. 跨文件引用全局变量或函数
  2. extern "C" 一起使用,用于 C++ 和 C 代码之间的链接兼容

1. extern 修饰符的基本用法

extern 用于标识变量或函数时,它告诉编译器该变量或函数的定义在另一个编译单元中(例如另一个 .cpp 文件)。这使得多个编译单元可以共享全局变量或函数。

例子:

  • 文件1: file1.cpp
1
2
3
4
5
6
7
8
9
#include <iostream>

// 定义全局变量
int g_Int = 42;

// 定义函数
void printGlobal() {
std::cout << "Global variable g_Int: " << g_Int << std::endl;
}
  • 文件2: file2.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>

// 声明全局变量和函数(声明而非定义)
extern int g_Int;
extern void printGlobal();

int main() {
// 访问在 file1.cpp 中定义的全局变量
std::cout << "Accessing g_Int in file2.cpp: " << g_Int << std::endl;

// 调用在 file1.cpp 中定义的函数
printGlobal();

return 0;
}

解释:

  • file1.cpp 中,变量 g_Int 和函数 printGlobal 被定义。
  • file2.cpp 中,我们使用 extern 声明 g_IntprintGlobal。这告诉编译器它们的定义在另一个编译单元中(即 file1.cpp),并在链接阶段解决符号。

编译和链接:

1
2
g++ file1.cpp file2.cpp -o output
./output

输出结果:

1
2
Accessing g_Int in file2.cpp: 42
Global variable g_Int: 42

注意:

  • extern 只是声明变量或函数,并不分配内存或生成代码。定义(即分配内存或生成代码的操作)必须在某个编译单元中完成。
  • 如果在多个编译单元中定义同一全局变量而没有使用 extern,会导致链接错误。

  1. extern "C" 用于 C++ 和 C 的混合编程

C++ 支持函数重载,这意味着同名的函数可以根据参数类型不同而有不同的实现。为了实现这一特性,C++ 会对函数进行“名称修饰(name mangling)”,即改变函数的符号名(通常包含有关函数参数类型的信息)。这使得同名函数能够在符号表中共存。

而 C 语言不支持函数重载,因此 C 函数的符号名保持不变。为了让 C++ 和 C 代码能够互相调用,C++ 提供了 extern "C" 关键字,指示编译器在处理某些函数时,按照 C 的命名规则来处理,而不是进行 C++ 的名称修饰。

例子:

  • C 代码: c_code.c
1
2
3
4
5
#include <stdio.h>

void c_function() {
printf("This is a C function.\n");
}
  • C++ 代码: cpp_code.cpp
1
2
3
4
5
6
7
8
9
10
#include <iostream>

// 告诉编译器按 C 语言方式链接 c_function
extern "C" void c_function();

int main() {
std::cout << "Calling C function from C++:\n";
c_function(); // 调用 C 函数
return 0;
}

编译和链接:

1
2
g++ cpp_code.cpp c_code.c -o mixed_output
./mixed_output
1
2
Calling C function from C++:
This is a C function.

解释:

  • cpp_code.cpp 中,extern "C" 告诉编译器不要对 c_function 进行名称修饰,而是按 C 的方式保留符号名。
  • 这使得 C++ 可以调用用 C 编译的函数 c_function,并在链接时正常找到它。

3. externconst 的结合

在 C++ 中,const 全局变量的默认作用域是内部链接(类似于 static),这意味着它只能在定义它的编译单元中使用。如果想要在多个编译单元中共享 const 全局变量,可以使用 extern 修饰。

例子:

  • 文件1: const_file1.cpp
1
2
3
4
#include <iostream>

// 定义一个 const 全局变量
const int g_ConstInt = 100;
  • 文件2: const_file2.cpp
1
2
3
4
5
6
7
8
9
10
#include <iostream>

// 声明这个 const 全局变量
extern const int g_ConstInt;

int main() {
// 访问在 const_file1.cpp 中定义的 const 变量
std::cout << "Accessing g_ConstInt in const_file2.cpp: " << g_ConstInt << std::endl;
return 0;
}

编译和链接:

1
2
g++ const_file1.cpp const_file2.cpp -o const_output
./const_output

输出结果:

1
Accessing g_ConstInt in const_file2.cpp: 100

解释:

  • g_ConstInt 是一个 const 全局变量,默认只能在定义它的编译单元中访问。
  • 通过在 const_file2.cpp 中使用 extern 声明 g_ConstInt,使得我们可以在另一个编译单元中访问这个 const 变量。

  1. externstatic 的对比
  • extern:声明变量或函数在其他编译单元中定义,并告知编译器在链接时解决符号。
  • static:限制变量或函数的作用域为当前编译单元,不能在其他编译单元中访问。

例子:

  • 文件1: static_file1.cpp
1
2
3
4
5
6
7
8
#include <iostream>

// 定义 static 全局变量
static int g_StaticInt = 10;

void printStaticInt() {
std::cout << "g_StaticInt in static_file1.cpp: " << g_StaticInt << std::endl;
}
  • 文件2: static_file2.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>

// extern int g_StaticInt; // 试图声明 static 变量会导致链接错误

extern void printStaticInt();

int main() {
// 无法直接访问 static 变量
// std::cout << g_StaticInt << std::endl; // 错误:g_StaticInt 在 file1.cpp 中是静态的

// 调用函数访问 static 变量
printStaticInt();
return 0;
}

编译和链接:

1
2
g++ static_file1.cpp static_file2.cpp -o static_output
./static_output

输出结果:

1
g_StaticInt in static_file1.cpp: 10

解释:

  • g_StaticInt 是一个 static 全局变量,因此它的作用域仅限于 static_file1.cpp。尝试在 static_file2.cpp 中通过 extern 声明它会导致链接错误。
  • 不过,static_file2.cpp 仍然可以通过调用 file1.cpp 中公开的函数 printStaticInt 来间接访问 g_StaticInt

总结

  • extern 是一个声明关键字,用于告诉编译器某个变量或函数的定义在其他编译单元中。
  • extern "C" 用于在 C++ 中与 C 语言进行链接兼容,防止名称修饰。
  • externconst 可以结合使用,通过 extern 声明 const 全局变量,使其在多个编译单元中共享。
  • externstatic 是两个相对的关键字:extern 用于声明外部链接,static 用于限制内部链接。