按变量的有效作用范围可以将其划分为局部变量和全局变量;还可以按变量的存储方式为其划分存储种类。在C语言中变量有四种存储种类,即自动变量(auto)、外部变量(extern)、静态变量(static)和寄存器变量(register)。这四种存储种类与全局变量和局部变量之间的关系如图2.8所示。
图2.8 变量的存储种类
1.自动变量(auto)
定义一个变量时,在变量名前面加上存储种类说明符“auto”,即将该变量定义为自动变量。自动变量是C语言中使用最为广泛的一类变量。按照默认规则,在函数体内部或复合语句内部定义的变量,如果省略存储种类说明,该变量即为自动变量。习惯上通常采用默认形式,例如:
等价于
自动变量的作用范围在定义它的函数体或复合语句内部,只有在定义它的函数被调用,或是定义它的复合语句被执行时,编译器才为其分配内存空间,开始其生存期。当函数调用结束返回,或复合语句执行结束时,自动变量所占用的内存空间就被释放,变量的值当然也就不复存在,其生存期结束。当函数被再次调用或复合语句被再次执行时,编译器又会为它们内部的自动变量重新分配内存空间,但它不会保留上次运行时的值,而必须被重新赋值。因此自动变量始终是相对于函数或复合语句的局部变量。
2.外部变量(extern)
使用存储种类说明符“extern”定义的变量称为外部变量。按照默认规则,凡是在所有函数之前,在函数外部定义的变量都是外部变量,定义时可以不写extern说明符。但是,在一个函数体内说明一个已在该函数体外或别的程序模块文件中定义过的外部变量时,则必须使用extern说明符。一个外部变量被定义之后,它就被分配了固定的内存空间。外部变量的生存期为程序的整个执行时间,即在程序的执行期间外部变量可被随意使用,当一条复合语句执行完毕或是从某一个函数返回时,外部变量的存储空间并不被释放,其值也仍然保留。因此外部变量属于全局变量。
C语言允许将大型程序分解为若干个独立的程序模块文件,各个模块可分别进行编译,然后再将它们链接在一起。在这种情况下,如果某个变量需要在所有程序模块文件中使用,只要在一个程序模块文件中将该变量定义成全局变量,而在其他程序模块文件中用extern说明该变量是已被定义过的外部变量就可以了。
函数是可以相互调用的,因此函数都具有外部存储种类的属性。定义函数时如果冠以关键字extern即将其明确定义为一个外部函数。例如,extern int func2(char a,b)。如果在定义函数时省略关键字 extern,则隐含为外部函数。如果要调用一个在本程序模块文件以外的其他模块文件所定义的函数,则必须用关键字 extern 说明被调用函数是一个外部函数。对于具有外部函数相互调用的多模块程序,利用μVision51 集成开发环境很容易完成编译链接。
这个例子中有两个程序模块文件“ex1.c”和“ex2.c”,可以在μVision51环境下将它们分别添加到一个项目文件“ex.prj”中,然后执行Project菜单中的Make:Updat Project选项即可将它们链接在一起,生成OMF51绝对目标文件ex,绝对目标文件可以装入dScope51中进行仿真调试。
例2-31 多模块程序。
程序的执行结果为:
由于C语言不允许在一个函数体内嵌套定义另一个函数,为了能够访问不同文件中各个函数的变量,除了可以采用我们在前面介绍过的参数传递方法之外,还可以采用外部变量的方法。上面的例子就说明了这一点。需要指出的是,尽管使用外部变量在不同函数之间传递数据有时比使用函数的参数更为方便,但是当外部变量较多时,会增加程序调试排错时的困难,使程序不便于维护。另外,不通过参数传递而直接在函数中改变全局变量的值,有时还会发生一些意想不到的副作用。因此一般情况下最好还是使用函数的参数来传递数据。
3.静态变量(static)
使用存储种类说明符“static”定义的变量称为静态变量。在例2-3.1的模块2程序文件中使用了一个静态变量:static int a=5。由于这个变量是在函数fun1()内部定义的,因此称为内部静态变量或局部静态变量。局部静态变量不像自动变量那样只有当函数调用它时才存在,退出函数后它就消失,局部静态变量始终都是存在的,但只能在定义它的函数内部进行访问,退出函数之后,变量的值仍然保持,但不能进行访问。
还有一种全局静态变量,它是在函数外部被定义的,作用范围从它的定义点开始,一直到程序结束。当一个C语言程序由若干个模块文件所组成时,全局静态变量始终存在,但它只能在被定义的模块文件中访问,其数据值可为该文件内的所有函数共享,退出该文件后,虽然变量的值仍然保持着,但不能被其他模块文件访问。
局部静态变量是一种在两次函数调用之间仍能保持其值的局部变量。有些程序需要在多次调用之间仍然保持变量的值,使用自动变量无法实现这一点,使用全局变量有时又会带来意外的副作用,这时就可采用局部静态变量。
例2-32 局部静态变量的使用——计算并输出1~5的阶乘值。
程序执行结果:
在这个程序中,一共调用了5次计算阶乘的函数fac(i),每次调用后输出一个阶乘值i!,同时保留这个i!值,以便下次再乘(i+1)。由此可见,如果要保留函数上一次调用结束时的值,或是在初始化之后变量只被引用而不改变其值,则这时使用局部静态变量较为方便,以免在每次调用时都要重新进行赋值。但是,使用局部静态变量需要占用较多的内存空间,而且降低了程序的可读性,当调用次数较多时往往弄不清局部静态变量的当前值是什么。因此,建议不要多用局部静态变量。
全局静态变量是一种作用范围受限制的外部变量,它的有效作用范围从其定义点开始直至程序文件的末尾,而且只有在定义它的程序模块文件中才能对它进行访问。全局静态变量与我们在前面介绍过的单纯全局变量是有区别的。全局静态变量有一个特点,就是只有在定义它的程序文件中才可以使用它,其他文件不能改变其内容。
C语言允许进行多模块程序设计,一个较大型的程序可被分成若干个模块,分别由几个人来完成。如果各人在独立设计各自的程序模块时,有些变量可能只希望在自己的程序模块文件中使用,而不希望被别的模块文件引用,对于这种变量就可以定义为全局静态变量。
需要指出的是,全局静态变量和单纯全局变量都是在编译时就已经分配了固定的内存空间的变量,只是它们的作用范围不同而已。
对于函数也可以定义成具有静态存储种类的属性。定义函数时在函数名前面冠以关键字static即将其定义为一个静态函数。例如,static int func1(char x,int y)。使用静态函数可使该函数只局限于其所在的模块文件。由于函数都是外部型的,因此静态外部函数定义就限制了该函数只能在定义它的模块文件中使用,其他模块文件是不能调用它的。换句话说,在其他模块文件中可以定义与静态函数完全同名的另一个函数,分别编译并链接成为一个可执行程序之后,不会由于程序中存在相同的函数名而发生函数调用时的混乱。这一特点在进行模块化程序设计时是十分有用的。
4.寄存器变量(register)
为了提高程序的执行效率,C语言允许将一些使用频率最高的变量,定义为能够直接使用硬件寄存器的所谓寄存器变量。定义一个变量时在变量名前面冠以存储种类符号“register”即将该变量定义成为了寄存器变量。寄存器变量可以被认为是自动变量的一种,它的有效作用范围也与自动变量相同。
由于计算机中的寄存器是有限的,不能将所有变量都定义成寄存器变量。通常在程序中定义寄存器变量时只是给编译器一个建议,该变量是否能真正成为寄存器变量,要由编译器根据实际情况来确定。另一方面,C51编译器能够识别程序中使用频率最高的变量,在可能的情况下,即使程序中并未将该变量定义为寄存器变量,编译器也会自动将其作为寄存器变量处理。由此可见,尽管可以在程序中定义寄存器变量,但实际上被定义的变量是否真能成为寄存器变量最终是由编译器决定的。