参考《C Primer Plus》(第6版)
重要概念
c提供了许多不同的模型或存储类别在内存中存储数据。
- 对象(obect):值占用的一块物理内存。一个对象可以存储一个/多个值。
- 标识符(entity):程序指定硬件内存中对象的方式,用于访问对象。
可以用存储期来描述对象,用作用域和链接描述标识符。
- 存储期(storage duration):对象在内存中保存了多长时间。
- 标识符的作用域(scope)和链接(linkage)表明了程序的哪些部分可以使用该标识符。
作用域
- 作用域:程序中可访问标识符的区域。
块作用域(block scope)
块:整个函数体是一个块,函数里面任意的复合语句也是一个块。简单来说,一对“{}”括起来的区域是一个块。
定义在块中的变量具有块作用域,块作用域的可见范围是从定义处到包含该定义的块的末尾。到目前为止,我们使用的局部变量(包括函数的形式参数)都具有块作用域。
1 | void fun(int n) |
函数作用域(function scope)
函数作用域仅用于goto语句的标签,作用域从标签位置开始延续至函数结束。与块作用域相比,突破了块的限制。标签的函数作用域避免了两个块中使用相同标签的混乱。
1 | void fun(bool condition) |
函数原型作用域(function prototype scope)
函数原型作用域用于函数原型中的形参名。作用范围是从形参定义处到原型声明结束。
1 | //函数原型声明:只注重函数类型、参数个数、参数类型,不关心形参名。 |
形式参数:函数定义的函数头中声明的变量。
实际参数:函数调用时实际传入的参数。
函数声明时的参数是假名,不必与函数定义的形式参数一致。
只有在变长数组中,函数原型的形参名才有用。例如:
1 | void use_a_VLA(int n, int m, int a[n][m]); |
文件作用域(file scope)
定义在函数外面的变量,即全局变量,具有文件作用域。作用范围从定义处到该定义所在文件的末尾。
具有文件作用域的变量在内存中不动。
作用域总结
作用域类别 | 作用域范围 | 应用 |
---|---|---|
块作用域 | 从定义处到包含该定义的块的末尾。 | 局部变量(包括函数的形式参数)都具有块作用域。 |
函数作用域 | 从标签位置开始延续至函数结束。 | 用于goto语句的标签。 |
函数原型作用域 | 从形参定义处到原型声明结束。 | 用于函数原型中的形参名。 |
文件作用域 | 从定义处到该定义所在文件的末尾。 | 全局变量具有文件作用域。 |
链接
c预处理会将#include指令用包含的头文件替换掉。
翻译单元:源代码文件中所有#include指令被头文件替换掉后的文件。程序由多个源代码文件组成时,等于由多个翻译单元组成。
文件作用域变量的实际可见范围是一个翻译单元。
c变量有三种连接属性:外部链接、内部链接、无链接。
链接与作用域
链接 | 链接描述 | 作用域 |
---|---|---|
外部链接 | 变量可以在多文件程序中使用。 | 具有文件作用域,外部定义使用存储类别说明符static。 |
内部链接 | 变量可以在一个翻译单元内使用。 | 具有文件作用域,外部定义没有使用存储类别说明符static。 |
无链接 | 变量只属于定义它们的块、函数、原型私有。 | 具有块作用域、函数作用域、函数原型作用域。 |
1 | int a = 5; //变量a具有外部链接文件作用域 |
习惯称“内部链接的文件作用域”为“文件作用域” ,称“外部链接的文件作用域”为“全局作用域”或“程序作用域”。
多文件
程序由多个翻译单元组成。
一个源文件中定义式声明了一个外部变量,该源文件可初始化该变量,其他文件使用该变量前需要使用关键字extern声明它以获得使用权。
外部变量,静态外部链接变量。
存储期
c对象具有四种存储期:静态存储期、线程存储期、自动存储期、动态分配存储期。
静态存储期
具有静态存储器的对象,在程序执行期间一直存在。
静态变量在内存中不动,并不是值不变。
文件作用域变量具有静态存储期,无论是内部链接(有static)还是外部链接。
带有关键字static的块作用域变量也具有静态存储期。即,静态无链接存储类别。
1 | int a = 5;//具有外部链接文件作用域 |
线程存储期
并发程序设计中,具有线程存储期的对象,从声明时到线程结束一直存在。
以关键字_Thread_local声明一个对象时,每个线程都获得该变量的私有备份。
自动存储期
具有块作用域的变量通常都具有自动存储期,从进入定义变量的块到退出块时存在。进入时分配内存,退出块时释放内存。
自动变量所占的内存可以视为一个可重复利用的暂存区。目前我们使用的局部变量都是自动变量。
1 | void fun(int a)//a开始 |
特例:变长数组是从声明处到块的末尾。
属于自动存储类别的变量具有自动存储期、块作用域、无链接。
块作用域无链接意味着变量只能在块内通过变量名访问,不同的函数内可以有同名的变量名(但是占用不同的内存)。
动态分配存储期
(无描述)
存储期总结
存储期 | 存储期描述 | 作用域和链接 |
---|---|---|
静态存储期 | 在程序执行期间一直存在。 | 内部/外部链接文件作用域;加static无链接块作用域。 |
线程存储期 | 从线程声明时到线程结束一直存在。 | |
自动存储期 | 从进入定义变量的块到退出块时存在。 | 无链接,块作用域。 |
动态分配存储期 |
存储类别
c使用作用域、链接、存储期为变量规定了多种存储类别。
存储类别 | 存储期 | 作用域 | 链接 | 声明方式 |
---|---|---|---|---|
自动 | 自动 | 块 | 无 | 块内 |
寄存器 | 自动 | 块 | 无 | 块内,加关键字register |
静态外部链接 | 静态 | 文件 | 外部 | 所有函数外 |
静态内部链接 | 静态 | 文件 | 内部 | 所有函数外,加关键字static |
静态无链接 | 静态 | 块 | 无 | 快内,加关键字static |
1 | int a=1;//静态外部链接类别 |
寄存器变量
寄存器变量存在最快的可用内存中,幸运的话就存在CPU的寄存器中。访问和处理寄存器变量的速度更快。
绝大多数情况,寄存器变量和自动变量一样——无链接、块作用域、自动存储期。
“register”类似于一种申请,编译器可以忽略该申请,使得变量变成自动变量。由于寄存器的空间限制,可声明为“register”的数据类型有限。
1 | void fun(register int n){//请求成为寄存器变量的形参n |
静态外部链接变量
又称“外部存储类别”,属于该存储类别的变量成为外部变量,具有文件作用域、外部链接、静态存储器。
外部变量的定义式说明在所有函数外面。
使用关键字extern的情况:
1 | /*情况一*/ |
1 | /*情况二*/ |
静态内部链接变量
在所有函数外用static定义的变量,具有静态存储器、文件作用域和内部链接。
1 | static int a=1;//内部链接的变量 |
静态无链接变量(块作用域的静态变量)
具有块作用域、无链接,但是具有静态存储期。
静态的意思是变量在内存中原地不动,并不是说值不变。变量具有和自动变量一样的作用域,但是程序离开该函数的时候,变量不会消失。
在块中以static声明变量。
1 | void fun() |
不能在函数的形参中使用static。
1 | int fun(static int a);//不允许 |