关键词: C/C++ extern
0. 前言
在学习MIT 6.087的过程中,遇到了变量作用域范围的问题,其中对C语言关键字extern
的认识和理解并不够充分。搜索后发现了这篇英文博客,于是将之翻译修改,作为学习记录及总结。
1. 声名和定义
首先我们要明确的是,C语言关键词extern
的应用范围包括C的变量和函数。笼统地讲,关键字extern
延申了C语言变量和C语言函数的可视性,这可能就是它被命名为extern
的原因。
但在正式介绍extern
关键字之前,我们还是要复习一下声明和定义的概念。对变量和函数的声明仅仅意味着这个变量和函数存在于程序的某处而并没有分配内存。虽然没有分配内存,但对变量和函数的声明却起到了关键的作用,它决定了变量和函数的类型。对于C语言中的变量而言,当一个变量被声明时,程序便知道了变量的数据类型。对于C语言中的函数而言,当一个函数被声明时,程序便知道了传递给函数的参数,参数的数据类型,参数的顺序以及函数的返回值。这就是声明的作用。当我们定义变量和函数时,除了完成声明的作用外,程序也为变量或函数分配内存。由此,我们可认为定义是声明的超集(或声明是定义的子集)。从这个角度出发,我们可以得到结论的是:变量和函数可被多次声明,但只可被定义一次。理由也显而易见,程序不可能在内存中的两个不同位置存储相同的变量和函数。上述就是关于声明和定义的全部内容。
2. extern
关键字
2.1 C函数与extern
关键字
理解了声明和定义的概念,我们就能很清晰地理解extern
关键字的作用。先来看一个简单的例子,C语言函数使用extern
。默认情况下,C语言中函数的声明和定义都被预先添加关键字extern
。也就是说,即使我们在声明和定义函数时没有使用extern
关键字,它本身默认设定自动使用。举例来说,当我们编写的函数代码如下:
1 | int foo(int arg1, char arg2); |
extern
关键字其实只是在开头被隐藏了而已,编译器实际处理的代码是
1 | extern int foo(int arg1, char arg2); |
同理适用于C语言函数的定义。由此,无论何时我们定义了一个C语言函数,extern
关键字就会出现在函数定义的开始。由于函数可被多次声明但只可定义一次,我们可发现对函数的声明可被添加到多个C/H文件中,亦或者在单个C/H文件中多次出现。但函数的实际定义只能在项目程序中出现一次,即只能定义在一个C文件中。所以extern
关键字拓展了函数在整个程序中的可视性,任何程序中包含了已知函数声明的文件都可以调用声明的函数。换句话说,只要知道了函数的定义,C编译器便可知道函数定义的存在,并找到函数的定义完成编译。这就是C函数与extern
关键字的内容。
2.2 C变量与extern
关键字
现在再让我们来看一看extern
关键字在C变量上的使用。首先提出一个问题,我们如何在声明一个C变量时不去定义它?
大部分人可能觉得这个问题问得有点神经病。但回答这个问题对于理解有extern
关键字的C变量有着非常重要的意义。问题的答案是:
1 | extern int var; |
上述代码中,我们声明了一个名为var
的整型变量。需要明确的是,在这一步我们并没有定义变量var
,由此也就并没有为变量var
分配内存。由此我们可以根据需求,多次进行这种类型的声明。
那么新的问题又要出现了,现在我们如何定义一个变量了?可能这个问题显得更加神经病。答案显而易见:
1 | int var; |
上述代码中,我们声明并且定义了一个名为var
的整型变量(要记住定义是声明的超集哦🙃)。这时var
的内存也已经分配。在C函数中,我们知道extern
关键字是默认设定的。所以当我们定义一个函数时,我们可以在函数前添加extern
,不会产生任何问题。但C函数这种extern
的使用方式并不适用于C变量。如果我们设定extern
关键字是C变量的默认设定,那么C变量只会被声明,程序永远不会为C变量分配内存。因此,当我们需要对C变量仅仅声明而不去定义时,我们必须对C变量显式地使用extern
关键字。同时,由于extern
的可视性拓展能力,如果我们知道了变量的声明以及变量已被定义,那么通过extern
关键字我们可以在程序的任意位置使用此变量。
接下来通过一些具体的例子来进一步加深理解。
例子1:
1 | int var; |
代码分析:程序编译成功。变量var
被定义(隐式地被声明)为全局变量。
例子2:
1 | extern int var; |
代码分析:程序编译成功。变量var
仅被声明。在主程序中没有使用var
变量,所以程序没有出错。
例子3:
1 | extern int var; |
代码分析:程序编译出错。因为变量var
仅仅被声明而没有在程序任何地方定义。程序并没有为变量var
分配内存。所以当程序想修改一个没有内存的变量的值时,编译报错。
例子4:
1 |
|
代码分析:如果somefile.h
中包含着变量var
的定义,那么程序编译成功,否则编译失败。
例子5:
1 | extern int var = 0; |
代码分析:你猜猜这段程序能正常运行嘛?这里就又涉及到了C标准里的另一条规定。在C语言中,如果一个变量仅被声明,但在声明的同时也进行了初始化,这个变量依旧会被分配内存。也就是此时变量被认为是完成了定义。由此,上述程序可编译成功和运行。
3. 小结
- C变量和函数可被多次声明,但只可定义一次。
extern
关键字用来拓展C变量和函数的可视性。- 程序中的函数的可视性默认设定为整个程序,所以
extern
关键字对于C函数的定义和使用是冗余的。 - 当
extern
关键字用在C变量时,此变量只被声明而没有被定义。 - 第4条有个例外,即当使用了
extern
的变量被声明的同时也被初始化,此操作可等同考虑为此变量被定义。