关键字 extern 在 C 语言的使用
- 前言
- 变量或函数的声明和定义
- 声明和定义全局变量的最好方式
- 关键点 1:可以声明多次但初始化一次
- 关键点 2:默认存储类是 extern
- 关键点 3:extern 变量或程序对整个程序可见
- 关键点 4:extern 作用于变量
- 常见错误
- 参考
前言
extern
用于声明 C 语言中的外部变量和函数。这个修饰符用于所有数据类型,比如 int,float,double,array,pointer,structure,function 等- 范围(scope):不绑定到任何函数。作用域整个程序,是全局的
- 默认值(default value):全局变量的默认初始化值是 0(或 null)
- 生命周期(lifetime):直到整个程序执行结束
extern
告诉编译器变量或函数(非静态的)都可以在链接时找到。适用于在模块之间共享某些全局变量,但是不想把它们放在一个头文件,或者在一个头文件中定义它们- 大部分编译器编译器会优化程序确保它们不会为
extern
对象保留内存,因为编译器知道定义它们的模块会保留内存
- 大部分编译器编译器会优化程序确保它们不会为
变量或函数的声明和定义
- 声明(declaration):声明变量或函数存在程序的某个地方,但是不为它们分配内存。确定了变量或函数的类型
- 声明一个变量时,程序知道这个变量的类型;声明一个函数时,程序知道函数的参数、数据类型、参数顺序和函数返回类型
- 声明是编译器需要的用于接受对标识符的引用
- 定义(definition):既包含声明的作用,也为变量或函数分配内存。可以认为定义是声明的一个超集
- 因此一个函数或变量可以声明多次,但是只能定义一次(即同一个函数或变量不能存在两个位置)
- 定义是对标识符的实例化/实现
- 定义时链接器需要的用于链接对这些实体的引用
- 单一定义原则(One Definition Rule):编译单元不应该对任意变量、函数、类类型、枚举类型或模板有多余一个的定义
声明和定义全局变量的最好方式
- 声明和定义全局变量的清晰、可靠的方式是使用一个头文件,该头文件包含变量的
extern
声明- 定义这些变量的源文件和引用这些变量的源文件包含此头文件
- 对于每一个程序,有且只有一个源文件定义这些变量
- 对于每一个程序,有且只有一个头文件声明这些变量
- 这个头文件时重要的,它使能在独立的翻译单元(TU,translation units,源文件)之间交叉检查,同时确保一致性
- 完整的程序可能还需要全局函数。C99 和 C11 要求函数在使用之前必须是已经声明或定义过的。使用一个头文件包含全局函数的
extern
声明。也可以不加extern
- 尽量避免使用全局函数——可以使用全局变量
关键点 1:可以声明多次但初始化一次
一个特殊的
extern
变量或函数可以声明多次,但是只初始化一次。但是不可以声明一次,初始化多次#include <stdio.h> extern int i;//declaring variable i int i=25;//initializing variable i extern int i;//again declaring variable i int main() { extern int i;//again declaring variable i printf("%d\n", i); return 0; } // 输出:25
#include <stdio.h> extern void sum(int,int);//by default it is extern function int main() { extern void sum(int,int);//by default it is extern function int a=5,b=10; sum(a,b); return 0; } void sum(int a, int b) { printf("%d\n", a+b); }
#include <stdio.h> extern int i;//declaring variable i int i=25;//initializing variable i int main() { printf("%d\n", i); return 0; } int i=20;//again initializing variable i // 输出:编译错误(error: redefinition of ‘i’)
关键点 2:默认存储类是 extern
extern
是所有全局变量和函数的默认存储类(storage class),即全局变量和函数默认对整个程序可见,不需要声明或定义extern
函数。使用extern
关键字是多余的- 编译器会在全局函数的声明和定义前面自动加上
extern
在下面两个测试代码中,变量
i
都是extern
变量// test1.c #include <stdio.h> int i;//definition of i: by default it is extern variable int main() { printf("%d\n", i); return 0; } // 输出:0
// test2.c #include <stdio.h> extern int i;//extern variable int main() { printf("%d\n", i); return 0; } // 输出:编译错误,未定义的引用(undefined reference to i)
// test3.c #include <stdio.h> void sum(int,int);//by default it is extern function int main() { int a=5,b=10; sum(a,b); return 0; } void sum(int a, int b) { printf("%d\n", a+b); } // 输出:15
关键点 3:extern 变量或程序对整个程序可见
extern
关键字用于扩展变量或函数的可见性。如果全局声明一个extern
变量或函数,那么它的可见性是整个程序,这个程序可能包含一个或多个文件。比如一个 C 程序,包含两个文件one.c
和two.c
下面程序的输出是 30
//one.c #include <conio.h> int i=25;//by default extern variable int j=5;//by default extern variable //above two lines is initialization of variable i and j void main() { clrscr(); sum(); getch(); }
//two.c #include <stdio.h> extern int i;//declaration of variable i extern int j;//declaration of variable j //above tow lines will search the initialization statement of variable i and j either in two.c(if initialized variable id static and static) // or one.c(if initialized variable is extern) void sum() {//by default extern function int s; s = i + j; printf("%d\n", s); }
一个
extern
变量或函数有外部(external)链接,一个外部链接的变量或函数对所有文件可见extern
作用于函数只是告诉编译链接是外部的;作用于变量只声明变量而不会定义(初始化或实例化)变量
关键点 4:extern 作用于变量
只用于声明变量
- 当对变量使用
extern
修饰符时,它只用于声明(比如不会为这些变量分配内存)。因此在上述test2.c
中,编译器报错undefined symbol
。如果要定义变量(比如为extern
变量分配内存),必须初始化变量 初始化
extern
变量即是定义extern
变量#include <stdio.h> extern int i=10;//extern variable int main() { printf("%d\n", i); return 0; } // 输出:10 // warning: ‘i’ initialized and declared ‘extern’ // 如果声明时也提供了初始化,那么会为变量分配内存,该变量认为是被定义过的
编译警告参考warning in extern declaration
#include <stdio.h> extern int i;//extern variable int main() { return 0; } // 编译成功。只声明变量 i 但是未使用,不会报错
如果我们声明一个变量是
extern
,那么编译器会搜索这个变量是否已经初始化。如果已经初始化为extern
或static
则成功。否则编译器会报错修正:初始化为 static 仍然报错????
#include <stdio.h> int main() { extern int i;//it will search the initialization of i printf("%d\n", i); return 0; } int i=20;//initialization of extern variable i // 输出:20
#include <stdio.h> int main() { extern int i;//it will search the initialization of i printf("%d\n", i); return 0; } static int i=20;//initialization of static variable i // 输出:编译错误(error: static declaration of ‘i’ follows non-static declaration)
全局变量自动初始化
- 如果全局变量不适用
extern
关键字,编译器会使用默认值自动初始化extern
变量 extern
整数类型变量的默认初始化值是 0 或者 null#include <stdio.h> char c; int i; float f; char *str; int main() { printf("%d %d %f %s\n", c, i, f, str); return 0; } // 输出:0 0 0.000000 (null)
不能局部地初始化 extern 变量
不能在任何代码块内部局部地初始化
extern
变量,不论是声明时初始化还是初始化和声明分开。我们只能全局地初始化extern
变量#include <stdio.h> int main() { extern int i=10; printf("%d\n", i); return 0; } // 输出:编译错误(error: ‘i’ has both ‘extern’ and initializer)
#include <stdio.h> int main() { extern int i;//declaration of extern variable i int i=10;//try to locally initialize extern variable i printf("%d\n", i); return 0; } // 输出:编译错误(error: declaration of ‘i’ with no linkage follows extern declaration)
#include <stdio.h> extern int i;//declaration of extern variable i int main() { int i=10;//declare and define a local variable printf("%d\n", i);//the i is local return 0; } // 输出:10
#include <stdio.h> int main() { extern int i;//declaration of extern variable i, its memory is not allocated i=10;//try to change the value of variable i to 10, but it doesn't exist printf("%d\n", i); return 0; } // 输出:编译错误(两处错误:undefined reference to i)
不能写全局的赋值语句
- 在声明变量时给变量赋值叫做初始化(initialization)
不在变量声明时给变量赋值叫做赋值(assignment)
#include <stdio.h> extern int i;//declaring variable i int i=25;//initializing variable i i=20; int main() { printf("%d\n", i); return 0; } // 输出:编译错误(error: redefinition of ‘i’)
#include <stdio.h> extern int i;//declaring variable i int main() { i=20;//assignment printf("%d\n", i); return 0; } int i=25;//initialization // 输出:20
对类成员无效
extern "C"
被类成员忽略
常见错误
未定义的行为
- Undefined behavior:使用了一个带外部链接的标识符,但是程序中不存在该标识符的外部定义,或者没有使用此标识符但是有多处定义此标识符
外部定义
- External definitions:外部定义指一个外部声明,同事也是函数(除了内联函数)或对象的定义。如果一个表达式中使用了一个有外部链接的标识符(除了作为
sizeof
或_Alignof
运算符的操作数的一部分,这些运算符的结果是一个证书常数),程序的其它地方应该有且仅有一个对此标识符的外部定义 - 因此,如果一个声明为外部链接的标识符未在表达式中被使用,不应该有它的外部定义
多重外部定义
- Multiple external definitions:对于一个对象的标识符可能有多于一处的外部定义,这些定义可能有也可能没有显式使用
extern
关键字;如果这些定义不一致,或者多于一处有初始化,就会导致 undefined behavior
头文件中变量的声明
- 声明
int some_var;
:如果一个头文件不使用extern
定义一个变量,那么每个包含此头文件的文件都会尝试创建一个此变量的一个定义。但是 C 标准不确保这个一定会正常工作 - 声明
int some_var = 13;
:如果头文件定义并初始化一个变量,那么在给定的程序中只有一个源文件可以使用这个头文件。因为头文件主要是用来共享信息的,创建一个只能使用一次的头文件不是好的做法 - 声明
static int some_var =3;
:如果头文件定义一个静态变量(不论是否初始化),那么每个源文件都会有此“全局”变量的一份私有拷贝。而且,如果这个变量是一个复杂的数组,那么会导致大量代码的拷贝