C语言在内存管理方面给我们提供了较为自由的操作,但我们在享有这些自由的同时,也要了解相关的规定,不然就会给程序带来很多漏洞,同时我们也要详细来了解存储类别,为以后的自由分配内存打好基础.(文章内容稍长,干货满满,可收藏后细细品看)
在了解存储类别之前,我先帮大家温习一遍相关的知识点
1.作用域
一个变量的作用域可以是块作用域,函数作用域,函数原型作用域及文件作用域
(其中函数作用域,函数原型作用域,不作为本文章重点,大家可以简单了解一下)
(1)块作用域:块就是用一对花括号括起来的区域,一般我们创建的变量都具有块作用域,具有块作用域的变量,有效区域是从定义处到包含该定义的块的末尾
看下面一段代码
int main() { int a=0; //a的作用域开始 for(a=0;a<3;a++) //注:for循环中的块,属于main函数块中的一个内层块 { int b=0; //b的作用域开始 } //b的作用域结束,即只有在for循环这个块中才能使用b } //a的作用域结束,可在整个main函数内使用a
需要注意的是在C99标准中,把块的概念扩展到循环和if语句所控制的代码中,即使没有被花括号括起来,它们仍属于这个块
看下面一段代码
int main() { for(int a=0;a<3;a++) //其中a属于for循环的一部分,a的作用域在for循环的块中 { //离开for循环的块,a的作用域就会结束 int b=0; } //到这里,a与b的作用域都结束了 }
同理,一个函数的形式参数,虽然定义在左花括号之前,但它仍属于这个函数,其作用域在这个函数块中
(2)函数作用域:仅用于goto语句的标签
这意味着即使一个标签首次出现在函数的内层块中,他的作用域可遍布至整个函数
参考下面一段代码
虽然标签flag定义在while循环块内,但它仍可作用于在它之前的if语句判断后的代码
(3)函数原型作用域:用于函数原型中的形参名(变量名)
函数原型作用域的范围从形参定义到函数原型声明结束(这里再帮大家温习一下函数的相关定义)
看如下代码
int add(int m, int n); //函数原型,告诉编译器add函数的类型 int main() { int a = 3, b = 4,c; c=add(a, b); //函数调用,在此执行add函数 printf("%d", c); } int add(int x,int y) //函数定义,指定了函数要做什么 { return x + y; }
事实上,编译器在处理函数声明中的形参时,只关心其类型,形参名可有可无,即使有,其形参名也不必与函数定义中的形参名相同,但在变长数组中有个例外
void add( int x, int y ,int arr [x][y] );
//若前两个变量作为第三个数组的参数,其变量名是不能乱改和不写的,方括号内要写上x,y
(4)文件作用域:若一个变量具有文件作用域,则其作用域从定义处到所在文件末尾都有效
看如下代码
int a = 3; //变量a具有文件作用域,该文件的函数都可以引用它 void print(void) //这样的变量也被称为全局变量 { printf("%d\n", a); } int main() { printf("%d\n", a); print(); return 0; }
2.链接
介绍完作用域,我们来了解一下链接(往后涉及到文件的概念,忘记的同学可以复习一下再看)
先介绍一下翻译单元的概念,编译器会将一个源代码文件以及该源代码文件所包含的各种头文件看成一个单独文件,这个单独的文件被称为翻译单元,即一个翻译单元对应一个源代码文件及其所包含的各种头文件
如下图, 实现一个简易信息库程序,创建了一个test.c的源文件,两个头文件contact.c , contact.h以及我们包含的各种stdio.h等头文件,在程序运行时,编译器会将其看成一个翻译单元。
C语言有3种链接属性:外部链接,内部链接,无链接
无链接:具有块作用域,函数作用域或函数原型作用域的变量都是无链接的变量。
内部链接和外部链接是相对于文件作用域而言的,内部链接只能作用于定义它的那个翻译单元,而外部链接可以作用于所有的翻译单元(较复杂的程序,会有多个源文件,即多个翻译单元)
举个例子,如下图
图中全局变量a就具有外部链接属性,源文件a组成的翻译单元与源文件b组成的翻译单元都可以使用变量a
但是变量b被static修饰,具有内部链接属性,只有源文件a这个翻译单元可以使用
如果源文件b想要引用变量a,一般要在引用时声明 extern int a; 表明这个变量来自其他文件,需要注意一点就是变量a的赋值只能是常量表达式,如果不给a赋初始值,a会被自动赋值为0,并且在b文件声明引用a时,不能给a赋值,例如extern int a=10;这是错误的。
注:具有文件作用域的变量都存储在内存的静态区中,直到程序结束才会将空间返还给内存,这与具有块作用域的变量不同,具有块作用域的变量在声明处被创建,出了包含它的块后,会自动将空间返还给内存。因此文件作用域的变量具有静态存储期,块作用域的变量具有自动存储期
(存储期:可简单理解为通过标识符访问的对象的生存期,静态存储期在程序执行期会一直存在,自动存储期会根据其作用域来判断返还超出作用域的变量的空间)
简单来说:作用域和链接是描述相应标识符的可用范围,而存储期是描述相应标识符的生存期
寄存器变量:
寄存器是在CPU上,不属于内存,因此,不可以取声明在寄存器上的变量的地址,在寄存器上声明变量需要用关键字register,并且不一定能够申请到,其作用域属于块作用域。
综上,我们可以得到以下一些存储类类别
(默认情况下,声明在块或函数头中的任何变量都属于自动存储类别)
存储类别 | 存储期 | 作用域 | 链接 | 声明方式 |
自动 | 自动 | 块 | 无 | 块内 |
寄存器 | 自动 | 块 | 无 | 块内,用关键字register |
静态外部链接 | 静态 | 文件 | 外部 | 所有函数外 |
静态内部链接 | 静态 | 文件 | 内部 | 所有函数外,用关键字static |
静态无链接 | 静态 | 块 | 无 | 块内,用关键字static |