存储类、链接和内存管理

C语言为变提供了5种不同的存储模型,或称存储类,还有基于指针的第6种存储模型。可以按照一个变量(或者说一个数据对象)的存储时期(storage duration)描述它,也可以按照它的作用域(scope)以及它的链接(linkage)来描述它。

  • 存储时期就是变量在内存中保留的时间;
  • 变量的作用域和链接一起表明程序的哪些部分可以通过变量名来使用该变量;
  • 不同的存储类提供了变量的作用域、链接以及存储时期的不同组合。
1.作用域 作用域可以是
  • 代码块作用域;
  • 函数原型作用域;
  • 文件作用域
代码块作用域 一个函数体是一个代码块,或者函数体内的一个复合语句也一个代码块,另外,函数的形式参量尽管在函数的开始花括号前进行定义,同样也具有代码块作用域,属于包含函数体的代码块。
double blocky(double cleo) { double p = 0.0; //... return p; }

变量cleo和p都具有代码块作用域。
函数原型作用域 function prototype scope,适用于声明函数原型中使用的变量名。
int mighty(int mouse, double large);
函数原型作用域从变量定义处一直到原型声明的末尾,意味着编译器在处理函数原型时,只关心参数的类型,并不关心参数名,这就解释通了,在函数原型的声明中,可以省略参数名。
文件作用域 在函数之外定义的变量,具有文件作用域,文件作用域的变量从它定义之处开始到文件的结尾都是可见的。
#include int myvar = 0; /* 具有文件作用域的变量 */ void main(void) { //... }

2.链接 C语言中变量有
  • 外部链接;
  • 内部链接;
  • 空链接;
空链接 具有代码块作用域或者函数原型作用域的变量具有空链接,空链接的变量在定义的代码块内或函数原型私有的,其他部分的代码访问不了这个变量。
外部链接或内部链接 具有文件作用域的变量具有外部链接,或者具有内部链接。
#include int giants = 5; // 文件作用域,具有外部链接 static int dodgers = 1; // 文件作用域,具有内部链接void main(void) { //... }

外部链接,不仅在本文件内可见,而且在其他文件也是可见的;内部链接只能在本文件内可见,是本文件私有的,使用关键词static修饰的文件作用变量,具有内部链接。
3.存储时期 C语言有两个存储时期
  • 静态存储时期(static storage duration)
  • 自动存储时期(automatic storage duration)
静态存储时期 具有文件作用域的变量具有静态储存时期,不管是内部链接的文件作用域还是外部链接的文件作用域,都具有静态储存时期,静态储存时期的变量在程序的执行期间将一直存在。关键词static修饰的变量是指内部链接,不是用于指静态存储时期的作用。
自动存储时期 具有代码块作用域的变量一般情况下具有自动存储时期。在程序进入代码块时,程序会为这些变量分配内存,当退出相应的代码块时,分配的内存会被释放。
C语言使用作用域、链接和存储时期来定义5种存储类:
  • 自动
  • 寄存器
  • 具有代码块作用域的静态
  • 具有外部链接的静态
  • 具有内部链接的静态
5种存储类
存储类 时期 作用域 链接 声明方式
自动 自动 代码块 代码块内
寄存器 自动 代码块 代码块内,使用字关键字register
具有外部链接的静态 静态 文件 外部 所有函数之外
具有内部链接的静态 静态 文件 外部 所有函数之外,使用关键字static
空链接的静态 静态 代码块 代码块内,使用关键字static
寄存器变量 一般来说,变量是存储中计算机内存中,不过,寄存器变量可以被存储在CPU寄存器中,这样,存储在速度最快的可用内存中,从而可以比普通变量更快地被访问和操作。
由于寄存器变量多是存放在一个寄存器而不是内存中,所以无法获得寄存器变量的地址,不过在其他的许多方面,寄存器变量与自动变量是一样的,也就是说,它们都有代码块作用域,空链接,自动存储时期,一般使用存储类说明符register来声明寄存器变量;
int main(void) { register int quick; //... }

或者形式参数也可以为寄存器变量:
void macho(register int m)
可以使用register声明的寄存器变量的类型是有限的,例如,处理器可能没有足够大的寄存器来容纳double类型。
具有外部链接的静态变量 具有外部链接的静态变量具有文件作用域,外部链接和静态存储时期,如:
int a; /* 定义外部链接的静态变量 */ double da[100]; /* 定义外部链接的静态变量 */ extern char c; /* 由于变量c已经在其他文件定义过,所以在这里再定义变量c,*/ /* 其实是引用其他文件定义的外部链接静态变量c,所以必须加extern关键词修饰 */void main(void) { extern int a; /* extern说明是引用了外部链接的静态变量a */double da[100]; /* 没有extern关键词修饰,则定义的是局部变量,代码块作用域的自动变量 */ /* 在代码块作用域内,默认是自动变量 */auto char c; /* 或者使用auto关键词显式地声明变量c为自动变量 */ }

存储类和函数 函数也有具有存储类。函数默认情况下是外部,当然也可以声明为静态函数,外部函数可以被其他文件的函数调用,而静态函数则只能在定义它的文件中使用。
double gamma(); /* 默认声明的函数是外部函数 */ static double release(); extern dbouel delele();

函数gamma()和delele()可以被其他文件的函数使用,而函数release()则只能在本文件内使用。
注意,函数delele()声明时使用extern关键词修改,并不是表明它是引用其他文件的函数,而是显式表明delele()是外部函数,可以被其他文件使用。
分配内存 主要函数malloc()和free() 函数malloc()可以用来返回数组指针、结构指针等等,因为一般需要把返回值的类型指派为适当的类型。
double * ptd; ptd = (double *) malloc(30 * sizeof(double));

这段代码返回请求30个double类型值的空间,并且把ptd指向该空间所在位置。
创建一个数组有3种方法:
  • 声明一个数组,声明时常用量表达式指定数组维数,然后可以用数组名访问数组元素;
  • 声明一个变长数组,声明时用变量表达式指定数组维数,然后用数组名来访问数组元素;
  • 声明一个指针,调用malloc(),然后使用该指针访问数组元素。
malloc()分配内存失败时没有足够的内存,会返回NULL。
一般地,对应每个malloc(),应用要调用一次free()释放内存。
free(ptd);

free()方法使用指针为入参。
类型限定词 volatile
限定词volatile告诉编译器该变量除了可被程序改变以外还可被其他代理改变。典型地,它被用于硬件地址和与其他并行运行的程序共享的数据。
例如,一个地址中可能保存着当前的时钟时间,不管程序做些什么,一眼该地址的值都会随着时间而改变。
volatile int locl; /* locl是一个易变的位置 */ volatile int * ploc; /* ploc指向一个易变的位置 */

其实volatile可以方便编译器优化:
val1 = x; //...other code... val2 = x;

编译器注意到程序再次使用了x,而没有改变它的值,编译器会把x临时存储在一个寄存器中,当val2需要x时,可以通过从寄存器而不是从内存位置中读取值,这样可以更快地节省时间,这个过程被称为缓存。
restrict
关键字restrict通过允许编译器优化某几种代码增强了计算支持。它只可用于指针,并表明指针是访问一个数据对象的唯一且初始的方式。
int ar[10]; int * restrict restar = (int *)malloc(10 * sizeof(int)); int * par = ar;

【存储类、链接和内存管理】这里,指针restar是访问由malloc()分配的内存的唯一且初始的方式,因此,它可以由关键字restrict限定,而par既不是初始的,也不是访问数组ar中数据的唯一方式,因此不能把par限定为restrict。

    推荐阅读