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);//不允许