C语言提供了多种存储类别(storage class)在内存中存储数据,这样一块内存称为对象(与面对对象编程中的类对象不同)。一个对象可以存储一个或多个值,也可能并未存储实际的值。
在程序中,访问对象通过变量声明,声明创建了一个标识符(identifier),标识符可用来指定特定对象的内容。除了可以通过标识符指定对象的方式外,还可使用解引用指针的方式。
基本概念
为了更好地理解不同的存储类别,首先简单介绍几个概念。
作用域
作用域:程序中可访问标识符的区域,有以下几种类别:
- 块作用域:块作用域变量的范围是从定义处到包含该定义的块末尾。这里的块可理解成一对大括号
{}
。 - 函数作用域:仅用于
goto
语句标签。一个标签即使首次出现在函数的内层块中,它的作用域也延伸至整个函数。 - 函数原型作用域:用于原型中的形参名,范围是从形参定义处到原型声明结束。这也是函数原型中有无变量名无关紧要的原因。
- 文件作用域:变量定义在函数的外面,也即全局变量,范围从定义处到所在文件的末尾。
链接
C语言有3种链接属性:
- 无链接:具有块作用域、函数作用域或函数原型作用域的变量,变量属于定义的块或函数。
- 外部链接:外部链接变量在多文件程序中使用。
- 内部链接:内部链接变量只能在一个翻译单元种使用。
翻译单元:一个源代码文件与其包含的头文件。
区分外部链接和内部链接关键看是否使用了static
,若使用了,则说明是内部链接,属于文件私有,不能被其它文件使用。
存储期
存储期:通过标识符访问对象的生存期,存储期分为4种:
- 静态存储期:在程序执行期间一直存在。文件作用域变量具有静态存储期。
- 线程存储期:用于并发程序设计,从被声明到线程结束一直存在。以关键字
_Thread_local
声明对象时,每个线程都获得该变量的私有备份。 - 自动存储期:块作用域变量具有。程序进入定义变量的块时,分配内存,退出时释放内存。
- 动态分配存储期:动态分配内存的存储期是从
malloc()
函数分配内存到free()
释放内存,详情可戳此链接了解。
文件作用域变量无论是否使用
static
关键字,都具有静态存储期,其中的static
表明链接属性,而非存储期。
块作用域变量也能具有静态存储期,只需在声明前加上关键字static
即可,从程序被载入到结束期间都存在,但是作用域还是在块内。
存储类别
本文介绍5种存储类别,如下表所示
存储类别 | 存储期 | 作用域 | 链接 | 声明 |
---|---|---|---|---|
自动 | 自动 | 块 | 无 | 块内 |
寄存器 | 自动 | 块 | 无 | 块内,使用关键字register |
静态无链接 | 静态 | 块 | 无 | 块内,使用关键字static |
静态外部链接 | 静态 | 文件 | 外部 | 函数外 |
静态内部链接 | 静态 | 文件 | 内部 | 函数外,使用关键字static |
程序把静态对象、自动对象和动态分配的对象存储在不同的区域。
静态存储类别所用的内存数量在编译时确定,在程序开始时被创建,在程序结束时销毁。
自动存储类别的变量在变量定义处创建,在定义所在的块结束处销毁。存储自动变量的内存会被作为栈来处理。
而动态分配的内存由程序员管理,这块区域通常称为内存堆或自由内存,动态内存通常比栈内存慢。
自动变量
可以显示地使用关键字auto
表明变量是自动存储类别,关键字auto
是存储类别说明符,例如
int main()
{
auto int a;
}
也可不使用auto
,默认情况就是如此。块作用域和无链接意味着只有在变量定义所在的块内才能通过变量名访问。自动变量不会被初始化。
auto
的作用在C和C++种完全不同,写兼容程序时应当避免。
寄存器变量
寄存器变量存储在CPU寄存器中,访问和处理这些变量的速度更快,但无法获取寄存器变量的地址。寄存器变量用register
声明,如
int main()
{
register int a;
}
但是这种方式不一定能过够获得寄存器变量,因为编译器会根据寄存器的实际情况做出选择,因此也有可能会忽略,这样一来,寄存器变量就变成了自动变量,但仍然不能使用地址运算符。
静态无链接变量
静态变量中的静态的含义是变量在内存中的位置不变,程序离开它们所在的函数后,变量并不会消失。块作用域的静态变量在声明时需要使用static
,例如
int main()
{
static int a;
}
这种变量又称为局部静态变量,这种存储类别也称为内部静态存储类别。
外部链接的静态变量
这种变量也称为外部变量,这种类别也叫外部存储类别。创建外部变量需要把变量的声明放在所有函数的外面。
如果一个文件使用的外部变量在另一个文件中,则声明变量必须使用extern
。
与自动变量不同的是,外部变量会自动被初始化为0。而且如果要初始化外部变量,只能使用常量表达式进行初始化。外部变量只能初始化一次,且必须是在定义该变量时进行。
内部链接的静态变量
这种变量也称为外部静态变量(这个名称有点矛盾),与外部链接静态变量一样定义在所有函数的外部,但需要使用static
声明。
程序默认的类别是自动存储类别。如果把变量设置为外部变量,虽然可以方便参数传递,但是容易在别的函数误改变量值,除非是const
数据。一个保护性程序的黄金法则是”按需知道“。
函数存储类别
函数有3种存储类别:
- 外部函数:可被其它文件的函数访问。
- 静态函数:只能用于其所在的文件。
- 内联函数。