存储类别

数据都存储在内存中,从硬件方面来讲,被存储的每个值都占用一定的物理内存,C语言把这样的一块内存称为对象(object)。对象可以储存一个或多个值。

1
int entity = 3;

entity 为标志符(identifier),是一个名称,可以用来指定(designate)特定对象的内容。

用存储期(storage dutation)描述对象,所谓存储器是指对象在内存中保留了多长时间。

用作用域(scope)和链接(linkage)描述标志符,标志符的作用域和链接表明了程序的哪些部分可以使用它。

作用域

  • 块作用域(block scope) 变量的可见范围是从定义处到包含该定义的块的末尾
  • 文件作用域(file scope) 变量定义在函数的外面,从它的定义处到该定义处所在文件的尾部均可见

翻译单元

编译器源代码文件和所有的头文件都看成是一个包含信息的单独文件,这个文件被称为翻译单元(translation unit)。描述一个具有文件作用域的变量时,它的实际可见范围是整个翻译单元。

链接

块作用域属于无链接变量,文件作用域可以是外部链接或内部链接。外部链接变量可以在多文件程序中使用(全局作用域或者程序作用域),内部链接变量只能在一个翻译单元中使用(文件作用域)。

1
2
3
4
5
int giants = 5; // 文件作用域,外部链接
static int dodgers = 3; // 文件作用域,内部链接
int main() {
// ...
}

存储期

  • 静态存储期 在程序执行期间一直存在,比如文件作用域变量
  • 线程存储期 用于并发程序设计,从被声明时到线程结束一直存在。以关键字 _Thread_local 声明一个对象,每个线程都获得该变量的私有备份
  • 自动存储期 当程序进入块时,块作用域变量会被分配内存,退出块时,分配给变量的内存会被释放
  • 动态分配存储期 使用malloc或calloc分配内存

寄存器变量

1
register int quick;

寄存器变量存储在CPU的寄存器中,无法获取寄存器变量的地址。声明变量为 register 类别与直接命令相比更像是一种请求。编译器必须根据寄存器或最快可用内存的数量衡量请求,或者直接忽略请求。忽略请求的情况下,寄存器变量就变成普通的自动变量,但仍然不能对该变量使用地址运算符。

外部链接的静态变量

外部链接的静态变量具有文件作用域、外部链接和静态存储期。该类别有时称为外部存储类别(external storage class),属于该类别的变量称为外部变量(external variable)。如果一个源码文件使用的外部变量定义在另一个源码文件中,必须用 extern 在该文件中声明该变量。

1
extern char Coal; // 如果Coal被定义在另一个文件中

编译器会假设 Coal 实际的定义在该程序的别处,也许在别的文件中。该声明并不会引起分配存储空间。因此不要用关键字 extern 创建外部定义,而只用它来引用现有的外部定义。

多文件

当程序由多个翻译单元组成时,才体现区别内部链接和外部链接的重要性。复杂的C程序通常由多个单独的源代码文件组成。如果外部变量定义在一个文件中,那么其他文件在使用该变量之前必须先声明它(用 extern 关键字)。

分配内存:malloc()、calloc()和free()

malloc()函数接受一个参数:所需内存字节数(函数名可以理解为memory alloc)。它会找到合适的空闲内存块,这样的内存是匿名的。内存分配成功时,返回动态分配内存块的首字节地址,分配失败则返回空指针。在 ANSI C 中,动态分配的内存应该坚持使用强制类型转换,提高代码的可读性。

1
2
3
4
5
6
double * ptd;
ptd = (double *) malloc(30 * sizeof(double));
if (ptd == NULL) {
    puts("Memory allocation failed. Goodbye.");
    exit(EXIT_FAILURE);
}

calloc()函数接受两个无符号整数(size_t)作为参数(函数名可以理解为count alloc),第一个参数是所需要的存储单元数量,第二个参数是存储单元的大小(以字节为单位)。

使用动态分配的内存后要使用 free()函数进行释放,否则会引起内存泄漏(memory leak)。