0%

C语言|关于作用域、链接、存储期、存储类别

参考《C Primer Plus》(第6版)

重要概念

c提供了许多不同的模型或存储类别在内存中存储数据。

  • 对象(obect):值占用的一块物理内存。一个对象可以存储一个/多个值。
  • 标识符(entity):程序指定硬件内存中对象的方式,用于访问对象。

可以用存储期来描述对象,用作用域链接描述标识符。

  • 存储期(storage duration):对象在内存中保存了多长时间。
  • 标识符的作用域(scope)和链接(linkage)表明了程序的哪些部分可以使用该标识符。

作用域

  • 作用域:程序中可访问标识符的区域。

块作用域(block scope)

块:整个函数体是一个块,函数里面任意的复合语句也是一个块。简单来说,一对“{}”括起来的区域是一个块。

定义在块中的变量具有块作用域,块作用域的可见范围是从定义处到包含该定义的块的末尾。到目前为止,我们使用的局部变量(包括函数的形式参数)都具有块作用域。

1
2
3
4
5
6
7
8
9
10
11
void fun(int n)
{
int a = 1;//a的作用域开始
if(n >= 1)
{
int b = a+a;//b的作用域开始
//只有内层块的代码可以访问b
a += b;
}//b的作用域结束
return a;
}//a的作用域结束

函数作用域(function scope)

函数作用域仅用于goto语句的标签,作用域从标签位置开始延续至函数结束。与块作用域相比,突破了块的限制。标签的函数作用域避免了两个块中使用相同标签的混乱。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void fun(bool condition)
{
//块1
if(condition)
{
label://块1的label的函数作用域开始
//...
}
//块2
if(!condition)
{
label://块2的label的函数作用域开始
//...
}
}//块1、块2的label作用域结束

函数原型作用域(function prototype scope)

函数原型作用域用于函数原型中的形参名。作用范围是从形参定义处到原型声明结束。

1
2
3
4
5
6
7
8
9
10
//函数原型声明:只注重函数类型、参数个数、参数类型,不关心形参名。
void fun(int mouse, double large);//mouse和large的作用域从这里开始、在这里结束。

//函数原型声明也可写作:void fun(int , double );

//函数定义
void fun(int m, double l)//m,l是形式参数,不必与函数声明时的mouse,large一致。
{
//定义内容
}

形式参数:函数定义的函数头中声明的变量。

实际参数:函数调用时实际传入的参数。

函数声明时的参数是假名,不必与函数定义的形式参数一致。

只有在变长数组中,函数原型的形参名才有用。例如:

1
2
3
void use_a_VLA(int n, int m, int a[n][m]);
// n开始----m开始----n,m被使用--n,m的函数原型作用域结束。
//方括号中使用的形参名是在该函数原型作用域中已声明的名称。

文件作用域(file scope)

定义在函数外面的变量,即全局变量,具有文件作用域。作用范围从定义处到该定义所在文件的末尾。

具有文件作用域的变量在内存中不动。

作用域总结

作用域类别 作用域范围 应用
块作用域 从定义处到包含该定义的块的末尾。 局部变量(包括函数的形式参数)都具有块作用域。
函数作用域 从标签位置开始延续至函数结束。 用于goto语句的标签。
函数原型作用域 从形参定义处到原型声明结束。 用于函数原型中的形参名。
文件作用域 从定义处到该定义所在文件的末尾。 全局变量具有文件作用域。

链接

c预处理会将#include指令用包含的头文件替换掉。

翻译单元:源代码文件中所有#include指令被头文件替换掉后的文件。程序由多个源代码文件组成时,等于由多个翻译单元组成。

文件作用域变量的实际可见范围是一个翻译单元。

c变量有三种连接属性:外部链接、内部链接、无链接。

链接与作用域

链接 链接描述 作用域
外部链接 变量可以在多文件程序中使用。 具有文件作用域,外部定义使用存储类别说明符static。
内部链接 变量可以在一个翻译单元内使用。 具有文件作用域,外部定义没有使用存储类别说明符static。
无链接 变量只属于定义它们的块、函数、原型私有。 具有块作用域、函数作用域、函数原型作用域。
1
2
int a = 5; //变量a具有外部链接文件作用域
static int b = 4; //变量b具有内部链接文件作用域

习惯称“内部链接的文件作用域”为“文件作用域” ,称“外部链接的文件作用域”为“全局作用域”或“程序作用域”。

多文件

程序由多个翻译单元组成。

一个源文件中定义式声明了一个外部变量,该源文件可初始化该变量,其他文件使用该变量前需要使用关键字extern声明它以获得使用权。

外部变量,静态外部链接变量。

存储期

c对象具有四种存储期:静态存储期、线程存储期、自动存储期、动态分配存储期。

静态存储期

具有静态存储器的对象,在程序执行期间一直存在。

静态变量在内存中不动,并不是值不变。

文件作用域变量具有静态存储期,无论是内部链接(有static)还是外部链接。

带有关键字static的块作用域变量也具有静态存储期。即,静态无链接存储类别。

1
2
3
4
5
6
7
int a = 5;//具有外部链接文件作用域
static b = 4;//具有内部链接文件作用域
void fun()
{
static int c = 1;//具有无链接块作用域
}
//a,b,c都具有静态存储期。

线程存储期

并发程序设计中,具有线程存储期的对象,从声明时到线程结束一直存在。

以关键字_Thread_local声明一个对象时,每个线程都获得该变量的私有备份。

自动存储期

具有块作用域的变量通常都具有自动存储期,从进入定义变量的块到退出块时存在。进入时分配内存,退出块时释放内存。

自动变量所占的内存可以视为一个可重复利用的暂存区。目前我们使用的局部变量都是自动变量。

1
2
3
4
5
6
7
8
9
10
11
12
void fun(int a)//a开始
{
int b;
{//c开始,内部a开始
int c;
int a;//内层的a会屏蔽外层的a的定义
}//c结束,内部a结束
}//a,b结束

//a,b,c都是自动类别。
//a,b在每次调用fun()时被创建,离开fun()时被销毁。
//c在每次进入块时被创建,离开块时被销毁。

特例:变长数组是从声明处到块的末尾。

属于自动存储类别的变量具有自动存储期、块作用域、无链接。

块作用域无链接意味着变量只能在块内通过变量名访问,不同的函数内可以有同名的变量名(但是占用不同的内存)。

动态分配存储期

(无描述)

存储期总结

存储期 存储期描述 作用域和链接
静态存储期 在程序执行期间一直存在。 内部/外部链接文件作用域;加static无链接块作用域。
线程存储期 从线程声明时到线程结束一直存在。
自动存储期 从进入定义变量的块到退出块时存在。 无链接,块作用域。
动态分配存储期

存储类别

c使用作用域、链接、存储期为变量规定了多种存储类别。

存储类别 存储期 作用域 链接 声明方式
自动 自动 块内
寄存器 自动 块内,加关键字register
静态外部链接 静态 文件 外部 所有函数外
静态内部链接 静态 文件 内部 所有函数外,加关键字static
静态无链接 静态 快内,加关键字static
1
2
3
4
5
6
7
8
9
int a=1;//静态外部链接类别
static b=2;//静态内部链接类别

void fun(int c)//自动类别
{
int d=3;//自动类别
static e=4;//静态无链接类别
register f=5;//寄存器类别
}

寄存器变量

寄存器变量存在最快的可用内存中,幸运的话就存在CPU的寄存器中。访问和处理寄存器变量的速度更快。

绝大多数情况,寄存器变量和自动变量一样——无链接、块作用域、自动存储期。

“register”类似于一种申请,编译器可以忽略该申请,使得变量变成自动变量。由于寄存器的空间限制,可声明为“register”的数据类型有限。

1
2
3
4
5
6
void fun(register int n){//请求成为寄存器变量的形参n
}

int main(){
register int a;//请求成为寄存器变量的a
}

静态外部链接变量

又称“外部存储类别”,属于该存储类别的变量成为外部变量,具有文件作用域、外部链接、静态存储器。

外部变量的定义式说明在所有函数外面。

使用关键字extern的情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*情况一*/
int a;//外部变量a的定义式声明
int aa[100];//外部变量aa数组的定义式声明
void fun();
int main()
{
extern int a;//可选的再次声明
extern int aa[];//可选的再次声明
/*这两条完全可以省略。如果去掉extern就会成为自动变量。*/
}
void fun()
{
extern int a;//a与上下文的a是同一个a。
int aa[2];//fun()里的自动变量
}
1
2
3
4
5
6
7
8
9
10
/*情况二*/
/*-------one.c--------*/
int b;//外部变量b的定义式声明

/*-------two.c--------*/
extern int b;//b被定义在另一个文件,则必须声明extern
int main()
{
//...
}

静态内部链接变量

在所有函数外用static定义的变量,具有静态存储器、文件作用域和内部链接。

1
2
3
4
5
static int a=1;//内部链接的变量
int main()
{
extern int a;//使用定义在别处的外部变量a,不是本文件的a。
}

静态无链接变量(块作用域的静态变量)

具有块作用域、无链接,但是具有静态存储期。

静态的意思是变量在内存中原地不动,并不是说值不变。变量具有和自动变量一样的作用域,但是程序离开该函数的时候,变量不会消失。

在块中以static声明变量。

1
2
3
4
5
6
7
8
9
10
void fun()
{
static int a=0;//静态无链接变量
a++;
}

//每次调用的时候a的值都还在
fun();//a=1
fun();//a=2
fun();//a=3

不能在函数的形参中使用static。

1
int fun(static int a);//不允许