C提供了多种不同的模型或存储类别(storage class)在内存中储存数据。要理解这些存储类别,先要复习一些概念和术语。标识符是C语言中用于标识唯一对象的符号,包括变量名、函数名、命令名称或常量名称等。
作用域描述程序中可访问标识符的区域,也就是说在程序的哪些部分可以使用该标识符
在C语言中,共有三种作用域
恩链接的主要作用是定义在程序具有多个文件时,是否可以跨文件访问。作用域和链接描述了标识符的可见性。
C语言共有三种链接属性
具有块作用域、函数作用域或函数原型作用域的变量都是无链接变量,意味着这些变量属于定义它们的块、函数或原型私有。
具有文件作用域的变量可以是外部链接或内部链接,通过关键字static
定义
int giants = 5; // 文件作用域,外部链接
static int dodgers = 3; // 文件作用域,内部链接
int main()
{
...
}
...
使用static
修饰的变量,具有内部链接属性。具有文件作用域的变量默认是外部链接。
对于变量dodgers
和变量giants
, 该文件中的任意函数都可使用它。变量dodgers
属文件私有,对于其他文件不可见,而对于变量giants
, 该文件和同一程序的其他文件都可以使用,只需要在引用这个变量的外部文件中声明
extern int giants;
上述代码中的extern
关键字告诉编译器,这不是一个定义,只是一个声明,表示这个变量位于其他文件中。不要用extern
进行外部定义,它只是用来引用一个已经存在的外部定义。
存储期描述了通过这些标识符访问的对象的生存期,即变量在内存中的生存时间。
C对象共有四种存储期
static
表明了其链接属性,而非存储期。以static
声明的文件作用域具有内部链接。但无论内部链接还是外部链接,所有的文件作用域变量都具有静态存储期。另外关键字static
还可以使得块作用域变量具有静态存储期。_Thread_local
声明一个对象时,每个线程都获得该变量的私有备份。malloc
函数,从声明到free
释放内存为止。C语言中,变量的属性信息不仅仅是其类型,还包括了作用域、链接和存储期。这些不同属性的组合方式呈现了变量的不同表现,下面开始讨论存储类别。
变量的存储类别取决于作用域
、链接
和存储期
。存储类别由声明变量的位置和与之关联的关键字决定,定义在所有函数外部的变量具有文件作用域、外部链接、静态存储期。声明在函数中的变量是自动变量,除非该变量前面使用了其他关键字。他们具有块作用域、无链接、自动存储期。以static
关键字声明在函数中的变量具有块作用域、无链接、静态存储期。以static
关键字声明在函数外部的变量具有文件作用域、内部链接、静态存储期。
C11新增了一个存储类别说明符:_Thread_local
,以该关键字声明的对象具有线程存储期,意思是线程中声明的对象在该线程运行期间一直存在,且在线程开始时被初始化,因此这种对象属于线程私有。
存储类别 | 存储期 | 作用域 | 链接 | 如何声明 |
---|---|---|---|---|
自动 | 自动 | 块 | 无 | 在块中 |
寄存器 | 自动 | 块 | 无 | 在块中,用关键字register |
静态、外部链接 | 静态 | 文件 | 外部 | 在所有函数外部 |
静态、内部链接 | 静态 | 文件 | 内部 | 在所有函数外部,使用关键字static |
静态、无链接 | 静态 | 块 | 无 | 在块中,使用关键字static |
线程、外部链接 | 线程 | 文件 | 外部 | 在所有块外部,使用关键字_Thread_local |
线程、内部链接 | 线程 | 文件 | 内部 | 在所有块的外部,使用关键字static和_Thread_local |
线程、无链接 | 线程 | 块 | 无 | 在快中,使用关键字static和_Thread_local |
属于自动存储类别的变量具有自动存储期
、块作用域
且无链接
。默认情况下,声明在块和函数头中的任何变量都属于自动存储类别,当然也可以显示使用关键字auto
。
自动存储期的变量意味着程序在进入该变量声明所在的块时变量存在,程序退出该块时变量消失。分配的空间被回收,可做他用。
块作用域和无链接意味着只有在该变量定义的块中才能通过变量名访问该变量(当然,参数用于传递变量的值和地址给另一个函数,这是间接的方法),并且内层块会隐藏外层块的定义。
关于自动存储类变量,需要注意其不会初始化,除非显示初始化,否则变量的初始值可能为任意值。
注意:关键字auto
是存储类别说明符(storage-class specifier)。auto
关键字在C++中的用法完全不同,如果编写C/C++兼容的程序,最好不要使用auto
作为存储类别说明符。
寄存器变量的属性和自动存储类几乎一样,都是块作用域
、无链接
和自动存储期
。使用存储类别说明符register
来声明寄存器变量。
int main (void)
{
register int quick;
register
类别声明了一个请求,编译器会根据寄存器或最快可用内存的数量衡量你的请求,从而加快访问速度;亦可能或略你的请求,使得寄存器变量变成普通的自动变量。但仍不能对该变量使用地址运算符。
块作用域的静态变量具有静态存储期
、块作用域
和无链接
,静态变量中的静态表示该变量在内存中保持不变,并不是值不变。之前提过,可以创建具有静态存储期
、块作用域
的局部变量,这些变量和自动变量一样,具有相同的作用域,但是程序离开其所在函数后,变量不会消失。即具有静态存储期
。在块中(提供块作用域
和无链接
)以存储类别说明符static
声明这种变量。
此处static
是第二种用法。对一个具有块作用域
的变量用static
修饰,改变了变量的存储期。
void trystat(void)
{
int fade = 1;
static int stay = 1;
printf("fade = %d and stay = %d\n", fade++, stay++);
}
上述代码中,静态变量stay
保存了它被递增1
后的值,但是fade
变量每次都是1
。这表明初始化的不同,每次调用trystat()
都会初始化fade
,但是stay
只在编译trystat()
时被初始化一次,之后每一次运行trystat
函数都不会为stay
分配新的内存空间,因为它一直存在着,所占用的空间从没被回收。
fade
声明每次调用函数时都会执行,这是运行时行为。而stay
声明实际上并不是trystat
函数的一部分,因为静态变量和外部变量在程序被载入内存时已执行完毕,把声明放在函数中只是告诉编译器只有在trystat
函数中才能看到该变量,这条声明未在运行时执行。
即静态存储期保证其在整个程序运行期间都存在,而块作用域确定只有在函数块内访问该变量。
外部链接的静态变量具有文件作用域
、外部链接
和静态存储期
。
该类别有时被称为外部存储类别(external storage class)
, 属于该类别的变量成为外部变量(external variable)
。把变量的定义性声明放在所有函数的外面便创建了外部变量。为了表明函数使用了外部变量,可以再函数中用关键字external
再次声明。如果一个源文件使用的外部变量定义在另一个源文件中,则必须用extern
在该文件中声明该变量。如下所示
int Errupt; // 外部存储类变量
double Up[100]; // 外部存储类变量
extern char Coal; // 必须声明,说明Coal被定义在其他文件中
void next(void);
int main(void)
{
extern int Errupt; // 可选声明
extern double Up[]; // 可选声明
...
}
void next(void)
{
...
}
若在main
函数中,有以下代码
int Errupt;
则该变量为自动变量,是一个独立的局部变量,与外部变量Errupt
不同。该局部变量Errupt
仅main
函数可见,而且在执行块中的语句时,会隐藏外部同名变量Errput
.而对于该文件的其他函数来说,外部变量Errupt
仍可见。
需要注意的是,若外部变量没有进行显示初始化,会自动初始化为0
,而且如果要对外部变量初始化,只能使用常量进行初始化。
int x = 10; // 没问题,10为常量
int y = 3 + 20; // 没问题,用于初始化的为常量表达式
size_t z = sizeof(int); // 没问题,用于初始化的为常量表达式
int x2 = 2 * x; // 不行,x为变量
该存储类别的变量具有静态存储期
、文件作用域
和内部链接
。在所有函数内部(这点与外部变量相同),用存储类别说明符static
定义的变量具有这种存储类别。此处的static
改变了变量的链接属性
static int svil = 1; // 静态变量,内部链接
int main(void)
{
普通的外部变量可用于同一程序中任意文件中的函数,但是内部链接的静态变量只能用于同一个文件中的函数。可以使用存储类别说明符extern
,在函数中重复声明任何具有文件作用域的变量,这样的声明并不会改变其链接属性。
C中共有6
个关键字作为存储类别说明符
auto
主要是为了明确表达要使用与外部变量同名的局部变量的意图static
用于文件作用域声明,作用域受限于该文件;如果static
用于块作用域声明,作用域受限于该块。因此,只要程序在运行对象就存在并保留其值,但只有在执行块内的代码时,才能通过标识符访问。块作用域的静态变量无链接。文件作用域的静态变量具有内部链接。extern
的声明具有文件作用域,则引用的变量必须具有外部链接。如果包含extern
的声明具有块作用域,则引用的变量可能具有外部链接或内部链接,这取决于该变量的定义式声明。typedef
关键字与任何内存存储无关,只是语法的原因被归于此类。注意:绝大多数情况下,不能在声明中使用多个存储类别说明符,所以这意味着不能使用多个存储类别说明符作为typedef
的一部分。唯一例外的是_Thread_local
,它可以和static
或extern
一起使用。