作用域和存储期
要创建大规模程序,必须首先理解作用域和存储期。
作用域和标识符的可见性
在如下代码所示的程序中对变量x的声明总共有三处。
/* 确认标识符的作用域 */ #include <stdio.h> int x = 75; /* A:文件作用域 */ void print_x(void) { printf("x = %d\n", x); } int main(void) { int i; int x = 999; /* B:块作用域 */ print_x(); //1 printf("x = %d\n", x); //2 for (i = 0; i < 5; i++) { int x = i * 100; /* C:块作用域 */ //3 printf("x = %d\n", x); //3 } printf("x = %d\n", x); //4 return 0; }
首先我们可以看到A处声明的x。该变量的初始值为75,因为它是在函数外面声明定义的,所以这个x拥有文件作用域。
因此,函数print_x中的“x”就是上述的代码中出现的第一个x,程序执行后,屏幕上会输出
x = 75 显示的是第一个x的值
因为1处调用了函数print_x,所以首先会进行上面的显示。
然后我们再来看B处声明的x。犹豫它是在main函数的程序块也就是复合语句中声明的,所以这个名称在main函数结尾的大括号}之前都是通用的
注意:
如果两个同名变量分别拥有文件作用域和块作用域,那么只有拥有块作用域的变量是“可见”的,而拥有文件作用域的变量会被“隐藏”起来。
由于B处的“x”就是它本身,因此x显示为
x == 999
在main函数的for语句中声明定义了第三个变量x。这里适用一下规则。
注意:
当同名变量都被赋予了块作用域的时候,内层的变量是“可见”的,而外层的变量会被“隐藏”起来。
综上所述,for语句循环体这个程序块中的“x”实际上就是上述第三个变量x。由于for语句的循环执行了5次,因此3处x的值显示为
x = 0
x = 100
x = 200
x = 300
x = 400
for语句的循环结束之后,该变量x的名称就会失效。因此,在调用最后一个printf函数的4处,x的值显示为
x = 999
被声明的标识符从其名称书写出来之后生效。
因此,即使把B处对x的声明修改为int x = x;,作为初始值的“x”也是被声明出来的x,而不是拥有文件作用域的在A处声明的那个x。因此,x的初始值不是75,而是被初始化为不确定的值。
存储期
在函数中声明的变量,并不是从程序开始到程序结束始终有效的。变量的生存期也就是寿命有两种,它们可以通过存储期(storage duration)这个概念来实现。
下面就通过程序代码来具体说明。
在函数func中声明了sx和ax两个变量。但是,声明sx的时候我们使用了存储类说明符static 。可能正因为如此,虽然是用相同的值进行初始化并递增的,但最终ax和sx的值并不相同。
自动存储期
在函数中不使用存储类说明符static而定义出的对象(变量),被赋予了自动存储期。它具有一下特性:
程序执行到对象声明的时候就创建出了相应的对象。而执行到包含该声明的程序块的结尾,也就是大括号}的时候,该对象就会消失。
也就是说,该对象拥有短暂的寿命,另外,如果不显式地进行初始化,则该对象会被初始化为不确定地值。
被赋予自动存储期地对像,在程序执行到 int ax = 0 ; 的时候,就被创建出来并且进行初始化。
静态存储期
在函数中使用static定义出来的对象,或者在函数外声明定义出来的对象都被赋予了静态存储期,它具有一下特性:
在程序开始执行的时候,具体地说是在main函数执行之前的准备阶段被创建出来,在程序结束的时候消失。
也就是说,该对象拥有“永久”的寿命。另外,如果不显式地进行初始化,则该对象会自动初始化为0.
被赋予了静态存储期地对象,回在main函数开始执行之前被初始化。因此,虽说程序执行的时候会经过static int sx = 0; 的声明,但其实那个时候并没有进行初始化处理,也就是说该声明并未执行赋值处理。
代码如下:
/* 自动存储期和静态存储期 */ #include <stdio.h> int fx = 0; /* 静态存储期+文件作用域 */ void func(void) { static int sx = 0; /* 静态存储期+块作用域 */ int ax = 0; /* 自动存储期+块作用域 */ printf("%3d%3d%3d\n", ax++, sx++, fx++); } int main(void) { int i; puts(" ax sx fx"); puts("----------"); for (i = 0; i < 10; i++) func(); puts("----------"); return 0; }
下表中总结了两种存储期的性质:
自动存储期 | 静态存储期 | |
生成 | 程序执行到对象声明的时候被创建出相应的对象 | 在程序开始执行的时候被创建出来 |
初始化 | 如果不显式地进行初始化,则该对象会被初始化为不确定地值 | 如果不显式地进行初始化,则该对象会被初始化为0 |
消失 | 执行到包含该声明的程序块的结尾时,该对象就会消失 | 在程序结束的时候消失 |
PS:在函数中通过存储类说明符auto 或者register 声明定义出的变量,也被赋予了自动存储期通过auto int ax = 0;进行的声明和不用auto进行的声明在编译的时候时完全相同的。因此auto就显得有些多余了。
另外,使用register进行的声明 register int ax = 0; ,在源程序编译的时候,变量ax不是保存在内存中,而是保存在更高速的寄存器中。然而,由于寄存器的数量有限,所以也不是绝对的。
现在的编译技术已经十分先进了,哪个变量保存在寄存器中更好都是通过编译自行判断并进行最优化处理的。(不仅如此,保存在寄存器中的变量在程序执行的时候也可能发生改变)。
使用register进行声明也渐渐变得没有意义了。