前言
变量是程序中数据的存储空间的抽象。变量的存储方式可分为静态存储和动态存储两种。
静态存储变量通常是在程序编译时就分配一定的存储空间并一直保持不变,直至整个程序结束。在上一部分中介绍的全局变量的存储方式即属于此类存储方式。
动态存储变量是在程序执行过程中使用它时才分配存储单元,使用完毕立即释放。典型的例子是函数的形参。在函数定义时并不给形参分配存储单元,只是在函数被调用时,才予以分配,调用函数完毕立即释放。如果一个函数被多次调用,则反复地分配、释放形参变量的存储单元。
从以上分析可知,静态存储变量是一直存在的,而动态存储变量则时而存在时而消失。因此,这种由于变量存储方式不同而产生的特性称为变量的生存期,生存期表示了变量存在的时间。
生存期和作用域是从时间和空间这两个不同的角度来描述变量的特性。这两者既有联系,又有区别。一个变量的存储方式究竟属于哪一种存储方式,并不能仅仅从作用域来判断,还应有明确的存储模型说明。
变量的存储模型由作用域、链接点及存储期三大属性来描述。其中,存储期描述的是变量在内存中的生存时间。存储模型也经常被表达为存储类,共有以下5种存储模型。
一、自动auto
变量的声明语法为
<存储类型> 数据类型 变量名
auto为存储类说明符,可以说明一个变量为自动变量。该类具有动态存储期、代码块的作用域和空链接。如果变量没有初始化,它的值是不确定的。
代码块或者函数头部定义的变量,可使用存储类修饰符auto来明确标识属于自动存储类型。若没有使用auto修饰,也属于自动存储类型。
例如:
{
int i,j,k;
charc;
)
等价于:
{
auto int i,j,k;
auto charc;
}
自动变量具有以下特点:
- 自动变量的作用域仅限于定义该变量的模块内。在函数中定义的自动变量,只在该函数内有效。在复合语句中定义的自动变量,只在该复合语句中有效。
- 自动变量属于动态存储方式,只有在定义该变量的函数被调用时才给它分配存储单元,开始它的生存期。函数调用结束,释放存储单元,结束生存期。因此函数调用结束之后,自动变量的值不能保留。在复合语句中定义的自动变量,在退出复合语句后也不能再使用,否则将引起错误。
- 由于自动变量的作用域和生存期都局限于定义它的模块内(函数或复合语句内),因此不同的模块中允许使用同名的变量而不会混淆。即使在函数内定义的自动变量也可与该函数内部的复合语句中定义的自动变量同名,但读者应尽量避免使用这种方式。
例如:
int loop(int n)
{
int m;
m=2;
{
int i,m;//m,i的作用域
m=20;
for(i=m;i<n;i++)
printf(……);
}
//m作用域,i消失
return m;
}
二、寄存器
在一个代码块内(或在一个函数头部作为参量)使用修饰符register声明的变量属于寄存器存储类。register修饰符暗示编译程序相应的变量将被频繁使用,如果可能的话,应将其保存在CPU的寄存器中,从而加快其存取速度。该类与自动存储类相似,具有自动存储期、代码块作用域和空链接。如果没有被初始化,它的值也是不确定的。
使用register修饰符有几点限制。
- register变量必须是能被CPU寄存器所接受的类型,这通常意味着register变量必须是一个单个的值,并且其长度应小于或等于整型的长度(int char *p)。这与处理器的类型有关。
- 声明为register仅仅是一个请求,而非命令,因此变量仍然可能是普通的自动变量,没有放在寄存器中。
- 由于变量有可能存储在寄存器中,因此不能用取地址运算符“&”获取register变量的地址。如果有这样的写法,编译器会报错。
- 只有局部变量和形参可以作为register变量,全局变量不行。
- 实际上有些系统并不把register变量存放在寄存器中,而优化的编译系统则可以自动识别使用频繁的变量而把它们放在寄存器中。
三、静态和空链接
静态变量的类型说明符:static。在一个代码块内使用存储类修饰符static声明的局部变量属于静态空链接存储类。该类具有静态存储时期、代码块作用域和空链接。
静态变量的存储空间是在编译完成后就分配了,并且在程序运行的全部过程中都不会撤销。这里要区别的是,属于静态存储方式的变量不一定就是静态变量。
例如外部变量虽属于静态存储方式,但不一定是静态变量,必须由static加以定义后才能称为静态外部变量,或称静态全局变量。
对比图
静态变量可分为静态局部变量和静态全局变量。静态局部变量属于静态存储方式,它具有以下特点。
- 静态局部变量在函数内定义,它的生存期为整个程序执行期间,但是其作用域仍与自动变量相同,只能在定义该变量的函数内使用该变量。退出该函数后,尽管该变量还继续存在,但不能使用它。
- 可以对构造类静态局部量赋初值、例如数组。若未赋初值,则由系统自动初始化为0。
- 基本数据类型的静态局部变量若在说明时未赋初值,则系统自动赋予0。而对自动变量不赋初值。值就是不确定的,根据静态局部变量的特点,可以看出它是一种生存期为整个程序运行期的变量。虽然离开定义它的函数后不能使用、但如再次调用定义它的函数时,它又可以继续使用,并且保留了上次被调用后的值,
因此、当多次调用一个函数且要求在调用之前保留某些变量的值时,可以考虑采用静态局部变量。虽然用全局变量也可以达到上述目的,但全局变量有时会造成意外的副作用、因此仍以采用静态局部变量为宜。
四、静态和外部链接
未使用static修饰的全局变量属于静态、外部链接存储类。具有静态存储时期、文件作用域和外部链接。仅在偏译时初始化一次。如未明确初始化,它的字节也被设定为0。在使用外部变量的函数中使用extern关键字来再次声明。如果是在其他文件中定义的,则必须使用extern。
五、静态和内部链接
全局变量在关键字之前再冠以static就构成了静态的全局变量,属于静态、内部链接存储类。与静态、外部链接存储类不同的是、具有内部链接,使得仅能被与它在同一个文件的函数使用。这样的变量也是仅在编译时初始化一次。如未明确初始化,它的字节被设定为0。
这两者的区别在于非静态全局变量的作用域是整个源程序,但当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的;而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效、在同一源程序的其他源文件中不能使用。
由于静态全局变量的作用域局限于一个源文件内,只能被该源文件内的函数使用,因此可以避免在其他源文件中引起错误。
静态全局变量及非静态全局变量的区别示意图。
从以上分析可以看出,把局部变量改变为静态变量后改变了它的存储方式,即改变了它的生存期。把全局变量改变为静态变量后改变了它的作用域,限制了它的使用范围。因此static这个说明将在不同的地方所起的作用是不同的。
例如:源文件a.c
int a=10;/全局变量,静态外部链接
static int b=20:/静态全局变量,静态内部链接
intf()
{
int m=30;
return m;
}
源文件b.c
#include<stdio.h>
/*使用extern关键字,声明外部变量*/
extern int b;
extern int a:
int main()
{
printf("a=%d\n",a);
printf("b=%d\n",b);
return 0:
}
编译程序,出现以下错误:
error:In function*main':
static _b.c:(. text+0x22):undefined reference to'b'
collect2:Id returned 1 exit status
分析:本例说明了,普通全局变量,是外部链接,可以被其他文件引用。有static关键字修饰的静态全局变量,是内部链接,限制了变量只在当前文件使用。