13.4 存储类
C语言有五种存储类,到目前为止已介绍了其中的三种。形参和函数的局部变量在函数调用时自动分配栈空间,并在函数返回时自动释放,因此它们是auto存储类。从第6章知道,这些名称的作用域(该名称可见的程序区域)从声明处一直到该声明所在的函数结束。
auto 函数参数和局部变量的默认存储类;在函数调用时自动分配存储空间到栈上,在函数返回时释放存储空间。
函数名自己是extern存储类,这意味着它们对于链接器是可见的。如果函数原型在任何函数定义之前,那么这些函数就可以被程序中的任何其他函数调用。编译器为了翻译函数调用,需要知道下列有关函数的至关重要的信息:它的返回类型、它有多少个参数,以及参数的数据类型。语句
extern prototype
的目的就是提供这类信息,这样的例子已经在库的头文件中见过多次了。该语句不产生extern存储类的函数,它仅仅通知编译器存在这样的函数,并告诉编译器链接器知道去哪里找到它。图13-5给出了这两种存储类:auto和extern。灰色字表示的名称是auto存储类,粗体字表示的名称是extern存储类。
extern 链接器所知的名称的存储类。
|
void fun_one(int arg_one, int arg_two) { int one_local; . . . } int fun_two (int a2_one, int a2_two) { int local_var; |
图13-5 如前所见的auto和extern存储类
|
. . . } int main (void) { int num; . . . } |
图13-5(续)
图13-5的阴影区域标记了该程序的顶层。extern类是在该层上声明的所有名称的默认存储类。
13.4.1 全局变量
以前在一个程序的顶层见到的只是函数的声明,然而在顶层也可以(尽管通常不建议)声明变量。这样的变量名的作用域是从声明处一直到源文件结束,除非是在函数中将同样的名称声明为形参或局部变量。如果要在变量声明之前的源文件区域内或其他源文件中引用顶层变量,可以通过将以关键字extern开始的变量声明放在文件中的首次引用之前,以便通知编译器该变量的存在。这样的变量可以由程序中的所有函数进行访问,因此有时称为全局变量。图13-6给出了文件eg1.c中的extern存储类的int型变量global_var_x的顶层声明,以及文件eg2.c中使得该全局变量可以在整个文件中被访问的extern语句。只有定义声明,也就是eg1.c中的那一个能够给global_var_x分配空间;而以关键字extern开始的声明不分配内存,它只是为编译器提供信息。
全局变量(global variable) 可以由程序中的多个函数访问的变量。

图13-6 全局变量的声明
尽管有些应用不可避免地要使用全局变量,但是这样无限制地访问变量通常被认为会有害于程序的可读性和可维护性。全局访问与函数应该仅基于需要,严格通过像函数原型表示那样的文档接口访问数据的原则相冲突。然而,全局变量表示常量时,其使用不会降低程序可读性。本文始终使用全局可见常量宏,它们在阐明程序意义方面会有帮助,而不是阻碍。在图13-7中有两个全局名称,它们表示常量数据结构。因为我们计划初始化这些内存块而决不改变它们的值,所以让整个程序访问它们不会造成伤害。在我们的声明中包括了定义全局量的const类型限定符,在extern声明中也包括了const限定符,它使另外的函数能够访问全局量。该限定符通知编译器程序可以查看但不能修改这些区域。
图13-7还给出了第三个前面已介绍过的、称为typedef的存储类。将typedef包含在存储类集合里面仅仅是为了标记方便。就像在第7章和第11章见到的一样,typedef语句不分配存储空间!
表13-1给出了哪些函数可以访问全局量complex_zero和months,以及可以访问的原因。假定任何影响全局量访问的局部变量声明都已给出。
|
|
图13-7 使用extern存储类的变量
表13-1 图13-7中访问全局变量的函数及其原因
|
函 数 |
可以访问extern类的变量 |
原 因 |
|
f1_fun1和f1_fun2 |
complex_zero和months |
它们的定义在同一个源文件的complex_zero和months的顶层定义声明之后。这两个函数没有同名的参数或局部变量 |
|
f1_fun3 |
只有complex_zero |
它的定义在同一个源文件的complex_zero的顶层定义声明之后,并且没有同名的参数或局部变量 |
|
f2_fun1 |
无 |
它的定义在声明之前,而声明会通知编译器complex_zero和months的存在 |
|
f2_fun2和f2_fun3 |
complex_zero和months |
它们的定义在同一个源文件的包含关键字extern的声明之后,该关键字通知编译器全局名称complex_zero和months的存在。这两个函数没有同名的参数或局部变量 |
13.4.2 static和register存储类
C语言的其余存储类是static和register。将static关键字放在局部变量声明的开头,会改变为变量分配存储空间的方式。在下面的函数片段中比较变量once和many:
int
fun_frag(int n)
{
static int once=0;
int many=0;
…
}
每当调用函数fun_frag时,就会为作为auto存储类的变量many分配栈上的存储空间,而且many对于每次调用都会初始化为零。每当fun_frag返回时,many的空间就会被释放。相反地,static变量once的空间分配和初始化只有一次,而且是在程序执行之前。直到整个程序终止以前,该空间始终不会被释放。如果fun_frag改变了once的值,那么该值会在多次fun_frag调用之间保留。
static 在程序执行之前只分配一次存储空间的变量的存储类。
使用static局部变量保留从一个函数调用到下一个调用的数据通常是一个比较差的编程习惯。如果该函数的性能依赖于这些数据,那么该函数就不会再执行仅基于输入参数的变换了,而且从程序读者的角度来说,函数效用的复杂性就极大地增加了。
使用static局部变量不降低可读性的一种情况就是将它放在函数main中,这是由于该函数的返回会终止程序。在分配了相对较小的运行栈的系统上,可能会希望在函数main中将若干大型数组声明为static变量,这样的话这些数组就不会耗尽栈空间。
最后一种存储类是register,它与auto存储类非常接近,并且只能用于局部变量和参数。实际上,C语言的实现不要求将register变量与auto变量视为不同。指定变量为register存储类只是通知编译器一个事实,那就是该存储单元会被无数次频繁引用。通过选择register存储类,编程者表明了一种期望,那就是如果寄存器(一种在中央处理器内部的特殊的高速内存区域)可以用于该变量,那么程序的运行速度就会更快。该存储类的一个较好的应用场合是作为大型数组下标的变量。下面是static和register存储类的变量声明:
static double matrix[50][40];
register int row, col;
register 程序员要存储在寄存器中的自动变量的存储类。
练习
自测
重新阅读图11-12中的计量单位转换程序。
1. 明确下列在程序中使用的名称的存储类:
unit_max (load_units的第一个参数)
found(在函数search中)
convert
quantity(在函数main中)
2. 函数search中的哪个变量使用register存储类会是一个好主意?







