extern 关键字

转载 extern 关键字 菜鸟教程解释

1. 基本解释

extern 可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。此外 extern 也可用来进行链接指定。

例如在头文件中声明: extern int g_Int; 它的作用就是声明函数或全局变量的作用范围的关键字,其声明的函数和变量可以在本模块或其他模块中使用,记住它是一个声明不是定义,也就是说 B 模块(编译单元)要是引用模块(编译单元) A 中定义的全局变量或函数时,它只要包含 A 模块的头文件即可,在编译阶段,模块 B 虽然找不到该函数或变量,但它不会报错,它会在连接时从模块 A 生成的目标代码中找到此函数。

此外,当和 “C” 连用时,如: extern "C" void fun(int a, int b);则告诉编译器在编译 fun 这个函数名时按着 C 的规则去翻译相应的函数名而不是 C++ 的,C++ 的规则在翻译这个函数名时会把 fun 这个名字变得面目全非,可能是 fun@aBc_int_int#%$ 之类的东西,不同的编译器采用的方法不一样,为什么会造成这种后果,因为C++支持函数的重载。

2. 问题

2.1 指向类型 T 的指针并不等价于类型 T 的数组

在一个源文件里定义了一个数组:char a[6]; 在另外一个文件里用下列语句进行了声明:extern char *a;会得到非法访问的错误。

原因在于,指向类型T的指针并不等价于类型T的数组。extern char *a 声明的是一个指针变量而不是字符数组,因此与实际的定义不同,从而造成运行时非法访问。应该将声明改为 extern char a[]。所以在使用 extern 时候要严格对应声明时的格式,在实际编程中,extern 用在变量声明中常常有这样一个作用,例如在 *.c/cpp 文件中定义了一个全局的变量,这个全局的变量如果要被引用(另一个文件引用),就放在 *.h 中并用extern来声明(让另一个文件 include 这个 .h 文件)。

2.2 extern 声明

如果函数的声明中带有关键字 extern,仅仅是暗示这个函数可能在别的源文件里定义,没有其它作用。即下述两个函数声明没有明显的区别:extern int f(); 和 int f(); 当然,这样的用处还是有的,就是在程序中取代 include “*.h” 来声明函数,在一些复杂的项目中,应该习惯在所有的函数声明和变量声明前添加 extern 修饰

在 test1.h 中有下列声明

#ifndef TEST1H
#define TEST1H
// 声明全局变量g_str
extern char g_str[]; 
void fun1();
#endif

在 test1.cpp 中

#include "test1.h"
// 定义全局变量g_str
char g_str[] = "123456"; 
void fun1() { cout << g_str << endl; }

以上是 test1 模块,它的编译和连接都可以通过,如果我们还有 test2 模块也想使用 g_str,只需要在原文件中引用就可以了

#include "test1.h"
void fun2() { cout << g_str << endl; }

以上 test1 和 test2 可以同时编译连接通过,如果打开 test1.obj,你可以在里面找”123456” 这个字符串,但是你却不能在 test2.obj 里面找到,这是因为 g_str 是整个工程的全局变量,在内存中只存在一份,test2.obj 这个编译单元不需要再有一份了,不然会在连接时报告重复定义这个错误。

有些人喜欢把全局变量的声明和定义放在一起,这样可以防止忘记了定义,如把上面 test1.h 改为

// 这个时候相当于没有extern
extern char g_str[] = "123456"; 

然后把 test1.cpp 中的 g_str 的定义去掉,这个时候再编译连接 test1 和 test2 两个模块时会报连接错误,这是因为把全局变量 g_str 的定义放在了头文件之后,test1.cpp 这个模块包含了 test1.h 所以定义了一次 g_str,而 test2.cpp 也包含了 test1.h 所以再一次定义了 g_str,这个时候连接器在连接 test1 和 test2 时发现两个 g_str。如果你非要把g_str的定义放在test1.h中的话,那么就把test2的代码中 #include “test1.h” 去掉,换成:

extern char g_str[];
void fun2() { cout << g_str << endl; }

这个时候编译器就知道 g_str 是引自于外部的一个编译模块了,不会在本模块中再重复定义一个出来,这是由于无法在 test2.cpp 中使用 #include “test1.h”,那么 test1.h 中声明的其他函数你也无法使用了,除非也都用 extern 修饰,这样的话光声明的函数就要一大串,而且头文件的作用就是要给外部提供接口使用的,所以只在头文件中做声明

2.3 extern 和 static 声明

extern 和 static 不能同时修饰一个变量;并且 static 修饰的全局变量声明与定义同时进行,也就是说当你在头文件中使用 static 声明了全局变量后,它也同时被定义了;最后,static 修饰全局变量的作用域只能是本身的编译单元,也就是说它的 全局 只对本编译单元有效,其他编译单元则看不到它,如:

tes1.h 中

#ifndef TEST1H
#define TEST1H
static char g_str[] = "123456";
void fun1();
#endif 

tes1.cpp 中

#include "test1.h"
void fun1()
{
  g_str[0] = 'a';
  cout << g_str << endl;
}

test2.cpp 中

#include "test1.h"
void fun2() { cout << g_str << endl; }

int main()
{
  // a23456
  fun1(); 
  // 123456
  fun2(); 
}

这说明在内存中存在了两份拷贝给两个模块中的变量使用。正是因为 static 有以上的特性,所以一般定义 static 全局变量时,都把它放在源文件中而不是头文件,这样就不会给其他模块造成不必要的信息污染。

2.4 extern 和 const

C++ 中 const 修饰的全局常量有跟 static 相同的特性,即它们只能作用于本编译模块中,但是 const 可以与 extern 连用来声明该常量可以作用于其他编译模块中,如 extern const char g_str[];然后在源文件中别忘了定义:const char g_str[] = “123456”。