存储类、链接与内存分配
存储类
概述
c为变量提供了5种不同的存储模型,即存储类,还有基于指针的第6种存储模型。
存储类可以按照一个变量的存储时期来描述,也可以按照作用域以及链接来描述,不同的存储类提供了变量的作用域、链接以及存储时期的不同组合。
作用域
-
代码块作用域
在一个代码块中定义的变量具有代码块作用域,一个代码块包括一对花括号中的代码,for循环,while循环,do while循环,if语句控制的代码,从该变量被定义的地方到包含该定义的代码块的末尾该变量均可见
-
函数原型作用域
函数原型作用域从变量定义处一直到原型声明的末尾,使用的名字通常无关紧要
-
文件作用域
一个在所在函数之外定义的变量具有文件作用域,具有文件作用域的变量从它定义处到包含该定义的文件结尾处都是可见的,也被称为全局变量
链接
-
空链接
具有代码块作用域或者函数原型作用域的变量具有空链接
- 外部链接
具有文件作用域的变量,且无static
- 内部链接
具有文件作用域的变量,且有static
空链接说明该变量为代码块或函数原型所私有,内部链接表示可以在一个文件的任何地方使用,外部链接表示可以在一个多文件程序的任何地方使用
存储时期
-
静态存储时期
- 程序执行期间一直存在
- 具有文件作用域的变量具有静态存储时期
注:对于具有文件作用域的变量,关键词static表明链接类型,并非存储时期
-
自动存储周期
- 进入代码块时分配内存,退出代码块时释放内存
- 具有代码块作用域的变量具有自动存储时期
c中的存储类
存储类 | 时期 | 作用域 | 链接 | 声明方式 |
---|---|---|---|---|
自动 | 自动 | 代码块 | 空 | 代码块内 |
寄存器 | 自动 | 代码块 | 空 | 代码块内,关键字register |
具有外部链接的静态 | 静态 | 文件 | 外部 | 所有函数外 |
具有内部链接的静态 | 静态 | 文件 | 内部 | 所有函数外,关键字static |
空链接的静态 | 静态 | 代码块 | 空 | 代码块内,关键字static |
-
自动变量
- 可用关键词auto表示自动变量,如
auto int x;
auto称为存储类说明符 - 内层定义覆盖外层定义
- 自动变量如果不经过初始化,则其初值为任意值,不一定是0
- 可用关键词auto表示自动变量,如
-
寄存器变量
int main(){ register int x; return 0; }
- 无法获得变量的地址,无法对其使用地址运算符
- 申请寄存器变量不一定成功,如果失败将会变成自动变量,但仍然无法获得变量地址
- 使用关键字register
-
具有代码块作用域的静态变量
- 静态指的是文件变量的存储位置固定不动
- 和自动变量的区别在于,退出代码块之后并不会自动消失
- 对于函数参量不能使用static
- 如果不显示地对静态变量进行初始化,它们将被初始化为0
void trystat(void); int main(){ for(int i=0;i<3;i++){ trystat(); } return 0; } void trystat(void){ int fade=1; static int stay=1;//该赋值语句实际并不是函数的一部分,仅仅会在编译时执行一次,不在运行时执行,放在函数内部是为了说明只有该函数可见 printf("%d %d\n",fade++,stay++); }
-
具有外部链接的静态变量
int tern=1;//定义声明 int main(){ extern int tern;//引用声明(可省略) return 0; } void fnc(){ ... }
-
函数外部定义的外部变量,如果是在其他文件中定义,则必须在前面加上关键字extern
-
如果在函数内部使用外部变量,可以不用任何声明,也可以加上extern来二次声明(称为引用声明,在外部定义时称为定义声明)外部的变量,其中外部的数组在二次声明时不必加上数组大小
-
外部变量未经初始化默认初值为0,数组元素同样如此
-
外部变量的初始化只能用常量表达式来赋值,并且只可以进行一次初始化
只要类型不是变长数组,sizeof表达式也被认为是常量表达式
-
-
具有内部链接的静态变量
static int svil=1; int main(){ return 0; }
- 具有内部链接,只能被同一文件中的函数使用
- 可以在函数内部用extern再次声明,任何具有文件作用域的变量在函数内都能用extern声明
存储类说明符
说明符 | 功能 |
---|---|
auto | 表明变量具有自动存储时期 |
register | 申请将变量存储在寄存器中,以便更快的存取,同时不能获得变量地址 |
static | 代码块作用域变量:在程序运行过程中其值不变,文件作用域变量:表示具有内部链接 |
extern | 表明在声明一个别处已经定义的变量 |
存储类和函数
函数也具有存储类
- 外部函数:不加static,可以被其他文件中的函数调用
- 内部函数:加static,只能在定义它的文件中使用
注:尽量不要使用外部变量,或者外部函数。
分配内存
malloc与free
-
malloc函数返回的是一个
void*
类型的通用指针,从而可以转换成其他类型的指针而无冲突,如果分配失败,则返回空指针 -
free函数的参数应是一个指向malloc分配的内存块的指针,free函数只能释放malloc分配的内存空间
-
在函数中没有free的后果:
函数终止时,指向malloc分配内存的指针作为自动变量消失了,但是该指针指向的内存还在。我们无法访问这些内存,因为没有地址,而且由于没有free,也无法被再次分配出去
动态内存分配与变长数组
变长数组VLA | 动态内存分配malloc |
---|---|
在函数终止时内存自动释放 | 需要free函数释放内存 |
定义多维数组更方便 | 多维数组定义麻烦 |
存储类与动态内存分配
内存可划分为三个独立部分:
-
(具有外部链接、内部链接、空链接的)静态变量
在编译时就知道静态变量所需的内存数量,从程序开始时就存在,到程序结束时终止
-
自动变量
可理解为堆栈,在程序运行时按顺序加入,在消亡时按相反顺序移除
-
动态分配的内存
malloc产生,free释放,由程序员决定,可能会变成碎片状,速度往往比堆栈内存慢
ANCI C的类型限定词
-
类型限定词const
如果一个变量声明中带有关键字const,则不能通过赋值、增量或减量来修改该变量的值
const int * p;//p所指的值不能通过p来改变 int const * p;//同上 int * const p;//p本身不能改变
-
类型限定词volatile
告诉编译器该变量除了可被程序改变以外还可以别其他代理改变,比如一个地址中可能存放当前的时钟时间,不管程序做什么,该地址的值都会改变,从而方便编译器的优化,如果无该限定词,则可以将变量的值存入缓存,而有该限定词则不缓存
-
类型限定词restrict
表明指针是访问一个数据对象的唯一且初始的方式,从而方便编译器优化
int ar[10]; int * restrict restar=(int*)malloc(10*sizeof(int)); int * par=ar; for(int n=0;n<10;n++){ par[n]+=5; restar[n]+=5; ar[n]*=2; par[n]+=3; restar[n]+=3; }
有了关键字
restrict
,编译器可以将该代码优化成restar[n]+=8
,而其他的则无法优化,因为编译器不知道会不会还有别的指针会更改其中的值