储存类别、链接和内存管理
1.存储类别及作用域
C语言包含4种储存类型,见下表:
特征 | 自动储存类型 | 寄存器储存类型 | 静态储存类型 | 外部储存类型 |
---|---|---|---|---|
关键字 | auto | register | static | extern |
储存于 | 内存 | CPU寄存器 | 内存 | 内存 |
默认初始值 | 垃圾值 | 垃圾值 | 0或空白符 | 0或空白符 |
作用域 | 局限于块 | 局限于块 | 局限于块 | 全局 |
生命周期 | 块内 | 块内 | 存在于函数之间 | 存在于函数之间 |
块指的是写在左右花括号:“ { } ” 内的一组语句。局部变量是声明在块内的变量。
局部变量与全局变量的区别:
-
局部变量是声明在块或者函数内部的变量。局部变量的作用域局限于该块或者函数。局部变量如果没有初始化,将包含垃圾数据。
-
全局变量是在所有块和函数之前声明的变量。全局变量对所有在它之后声明的函数有效。全局变量有默认值初始化,如0。
作用域
作用域描述程序中可访问标识符的区域。一个C变量的作用域可以是块作用域、函数作用域、函数原型作用域或文件作用域。
-
块作用域
块是用一对花括号括起来的代码区域。例如,整个函数体是一个块,函数中的任意复合语句也是一个块。定义在块中的变量具有块作用域(block scope),块作用域变量的可见范围是从定义处到包含该定义的块的末尾。另外,虽然函数的形式参数声明在函数的左花括号之前,但是它们也具有块作用域,属于函数体这个块。 -
函数作用域
函数作用域(function scope)仅用于goto语句的标签。这意味着即使一个标签首次出现在函数的内层块中,它的作用域也延伸至整个函数。如果在两个块中使用相同的标签会很混乱,标签的函数作用域防止了这样的事情发生。 -
函数原型作用域
函数原型作用域(function prototype scope)用于函数原型中的形参名(变量名),如下所示:
int mighty(int mouse, double large);
函数原型作用域的范围是从形参定义处到原型声明结束。这意味着,编译器在处理函数原型中的形参时只关心它的类型,而形参名(如果有的话)通常无关紧要。而且,即使有形参名,也不必与函数定义中的形参名相匹配。只有在变长数组中,形参名才有用:
void use_a_VLA(int n, int m, ar[n][m]);
方括号中必须使用在函数原型中已声明的名称。 -
文件作用域
变量的定义在函数的外面,具有文件作用域(file scope)。具有文件作用域的变量,从它的定义处到该定义所在文件的末尾均可见。
2.链接
C变量有3种链接属性:外部链接、内部链接或无连接。
具有块作用域、函数作用域或函数原型作用域的变量都是无链接变量。这意味着这些变量属于定义他们的块、函数或原型私有。具有文件作用域的变量可以是外部链接或者内部链接。外部链接变量可以在多文件程序中使用,内部链接变量只能在一个翻译单元中使用。
链接详解
3.存储期
作用域和链接描述了标识符的可见性。存储期描述了通过这些标识符访问的对象的生存期。
C对象有4种存储期:静态存储期、线程存储期、自动存储期、动态分配存储期。
如果对象具有静态存储期,那么它在程序的执行期间一直存在。文件作用域变量具有静态存储期。注意,对于文件作用域变量,关键字 static表明了其链接属性,而非存储期。以static声明的文件作用域变量具有内部链接。但是无论是内部链接还是外部链接,所有的文件作用域变量都具有静态存储期。
线程存储期用于并发程序设计,程序执行可被分为多个线程。具有线程存储期的对象,从被声明时到线程结束一直存在。以关键字_Thread_local声明一个对象时,每个线程都获得该变量的私有备份。
块作用域的变量通常都具有自动存储期。当程序进入定义这些变量的块时,为这些变量分配内存;当退出这个块时,释放刚才为变量分配的内存。这种做法相当于把自动变量占用的内存视为一个可重复使用的工作区或暂存区。例如,一个函数调用结束后,其变量占用的内存可用于储存下一个被调用函数的变量。变长数组稍有不同,它们的存储期从声明处到块的末尾,而不是从块的开始处到块的末尾。
4.自动态变量
属于自动存储类别的变量具有自动存储期、块作用域且无链接。默认情况下,声明在块或者函数头中的任何变量都属于自动存储类别。
变量具有自动存储期意味着,程序在进入该变量声明所在的块时变量存在,程序在退出该块时变量消失。原来该变量占用的内存位置现在可做他用。
静态变量 | 自动变量 | 寄存器变量 |
---|---|---|
所有的代码块之外声明的变量 | 在代码块内部定义的变量 | 用register修饰的变量 |
储存于静态内存中 | 储存在堆和栈中 | 储存在寄存器中 |
程序运行之前创建,整个运行期间存在直至程序结束 | 控制流进入代码块时被创建,离开时被销毁 | 创建销毁时间与自动变量相同 |
5.块作用域的静态变量
静态变量(static variable)听起来像是一个不可变的变量。实际上,静态的意思是该变量在内存中原地不动,并不是说它的值不变。
具有文件作用域的变量自动具有(也必须是)静态存储期。
可以创建具有静态存储期、块作用域的局部变量。这些变量和自动变量一样,具有相同的作用域,但是程序离开它们所在的函数后,这些变量不会消失。也就是说,这种变量具有块作用域、无链接,但是具有静态存储期。计算机在多次函数调用之间会记录它们的值。在块中(提供块作用域和无链接)以存储类别说明符static(提供静态存储期)声明这种变量
6.外部链接的静态变量
外部链接的静态变量具有文件作用域、外部链接和静态存储期。该类别有时称为外部存储类别(external storage class),属于该类别的变量称为外部变量(external variable)。把变量的定义性声明(defining declaration)放在在所有函数的外面便创建了外部变量。当然,为了指出该函数使用了外部变量,可以在函数中用关键字extern再次声明。如果一个源代码文件使用的外部变量定义在另一个源代码文件中,则必须用extern在该文件中声明该变量。
如下所示:
int Errupt; /* 外部定义的变量 */
double Up[100]; /* 外部定义的数组 */
extern char Coal; /* 如果Coal被定义在另一个文件,则必须这样声明 */
void next(void);
int main(void)
{
extern int Errupt; /* 可选的声明*/
extern double Up[]; /* 可选的声明*/
...
}
void next(void)
{
...
}
注意,在main()中声明Up数组时(这是可选的声明)不用指明数组大小,因为第1次声明已经提供了数组大小信息。main()中的两条 extern 声明完全可以省略,因为外部变量具有文件作用域,所以Errupt和Up从声明处到文件结尾都可见。它们出现在那里,仅为了说明main()函数要使用这两个变量。
7.随机函数
需要包含头文件:
#include <stdlib.h>
rand()函数是按指定的顺序来产生整数,因此每次执行上面的语句都打印相同的两个值,所以说C语言的随机并不是真正意义上的随机,有时候也叫伪随机数,使用 rand() 生成随机数之前需要用随机发生器的初始化函数 srand(unsigned seed)(也位于 stdlib.h 中) 进行伪随机数序列初始化,seed 又叫随机种子,通俗讲就是,如果每次提供的 seed 是一样的话,最后每一轮生成的几个随机值也都是一样的,因此叫伪随机数,所以需要每次提供不同的 seed 达到完全的随机,我们通常用时间函数 time(NULL) 作为 seed ,因为时间值每秒都不同,这个函数需要包含以下头文件:
#include <time.h>
8.分配内存:malloc( )和free( )
看看大佬怎么说:
9.ANSI C类型限定符
1.const类型限定符
描述 | 示例 | 含义 |
---|---|---|
类型前 const | const float * p | 指针本身可以改变,指向的值不可改变 |
变量名前 const | float* const p | 指针本身不可改变,指向的值可以改变 |
类型,变量名前 const | const float* const p | 指针本身不可改变,指向的值不可改变 |
类型后,*之前 const | float const* p | 指针本身可以改变,指向的值不可改变 |
总的来说:const放在[ ]左侧,指针指向的数据不能改变,放在[ ]的右侧,指针本身不能改变
2.volatile类型限定符
volatile限定符告知计算机,代理(而不是变量所在的程序)可以改变该变量的值。通常,它被用于硬件地址以及在其他程序或同时运行的线程中共享数据。
(1)编译器会禁止对volatile修饰的变量进行读写优化。
volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。
(2)每一次读取volatile修饰的变量都会从内存中读取。
volatile可解释为“直接存取原始内存地址”;“易变”是因为外在因素引起的,像多线程,中断等,并不是因为用volatile修饰了的变量就是“易变”了,假如没有外因,即使用volatile定义,它也不会变化.
3.restrict类型限定符
restrict关键字允许编译器优化某部分代码以更好得支持计算。它只能用于指针,表明该指针是访问数据对象的唯一且初始的方式。
restrict是c99标准引入的,它只可以用于限定和约束指针,对象已经被指针所引用,不能通过除该指针外所有其他直接或间接的方式修改该对象的内容。即它告诉编译器,所有修改该指针所指向内存中内容的操作都必须通过该指针来修改,而不能通过其它途径(其它变量或指针)来修改;这样做的好处是,能帮助编译器进行更好的优化代码,生成更有效率的汇编代码.如 int *restrict ptr, ptr 指向的内存单元只能被 ptr 访问到,任何同样指向这个内存单元的其他指针都是未定义的,直白点就是无效指针。restrict 的出现是因为 C 语言本身固有的缺陷,C 程序员应当主动地规避这个缺陷,而编译器也会很配合地优化你的代码。
restrict限定符还可用于函数形参中的指针。这意味着编译器可以假定在函数体内其他标识符不会修改该指针指向的数据,而且编译器可以尝试对其优化,使其不做别的用途