变量的存储方式和生存期
动态存储方式与静态存储方式
从变量的作用域(即从空间)的角度来观察,变量可以分为全局变量和局部变量
从变量存在的时间(即生存期)来观察:有的变量在程序运行的整个过程都是存在的,而有的变量则是在调用其所在的函数时才临时分配存储单元,而在函数调用结束后该存储单元就马上释放了,变量不存在了。
变量的存储有两种不同的方式:静态存储方式和动态存储方式
静态存储方式:是指在程序运行期间由系统分配固定的存储空间的方式
动态存储方式:是指在程序运行期间根据需要进行动态的分配存储空间的方式
用户区的存储空间可以分为3个部分:
- 程序区
- 静态存储区
- 动态存储区
数据分别存放在静态存储区和动态存储区。全局变量全部存放在静态存储区,在程序开始执行时给全局变量分配存储区,程序执行完毕就释放。在程序执行过程中它们占固定的存储单元,而不是动态的进行分配和释放
在动态存储区存放以下数据:
- 函数形参。在调用函数时给形参分配存储空间
- 函数中定义的没有用关键字
static
声明的变量,即自动变量 - 函数调用时的现场保护和返回地址等
对于动态存储区的数据,在函数调用时分配动态存储空间,函数结束时释放这些空间
在程序执行过程中,这总分配和释放是动态的,如果在一个程序中两次调用同一函数,而在此函数中定义了局部变量,在两次调用时分配给这些局部变量的存储空间的地址可能是不相同的
如果一个程序中包含若干个函数,每个函数中的局部变量的生存期并不等于整个程序的执行周期,它只是程序执行周期的一部分。在程序执行过程中,先后调用各个函数,此时会动态的分配和释放存储空间
在 C 语言中,每一个变量和函数都有两个属性:数据类型和数据的存储类别。对数据类型,如整型、浮点型等。存储类别指的是数据在内存中存储的方式(如静态存储和动态存储)
在定义和声明变量和函数时,一般应同时指定其数据类型和存储类别,也可以采用默认方式指定(即如果用户不指定,系统会隐含的指定为某一种存储类别)
C 的存储类别包括4种:自动的(auto)、静态的(static)、寄存器的(register)、外部的(extern)根据变量的存储类别,可以知道变量的作用域和生存期
局部变量的存储类别
自动变量(auto 变量)
函数中的局部变量,如果不专门声明为 static
(静态)存储类别,都是动态的分配存储空间的,数据存储在动态存储区中。函数中的形参和在函数中定义的局部变量(包括在复合语句中定义的局部变量),都属于此类。在调用该函数时,系统会给这些变量分配存储空间,在函数调用结束时就自动释放这些存储空间。因此这类局部变量称为自动变量。自动变量用关键字 auto
作存储类别的声明
auto 变量类型 变量名
实际上,关键字“auto”可以省略,不写 auto 则隐含指定为“自动存储类别”,它属于动态存储方式。程序中大多数变量属于自动变量
auto 变量类型 变量名
等价于变量类型 变量名
静态局部变量(static 局部变量)
有时希望函数中的局部变量的值在函数调用结束后不消失而继续保留原值,即其占用的存储单元不释放,在下一次再调用该函数时,该变量已有值(就是上一次函数调用结束时的值)这时就应该指定该局部变量为“静态局部变量”,用关键字 static
进行声明
静态局部变量都属于静态存储类别,在静态存储区内分配存储单元。在程序整个运行期间都不释放
自动变量(即动态局部变量)属于动态存储类别,分配在动态存储区空间而不在静态存储区空间,函数调用结束后释放
对静态局部变量是在编译时赋初值,即只赋初值一次,在程序运行时它已有初值。以后每次调用函数时不再重新赋初值而只是保留上次函数调用结束时的值
对自动变量赋初值,不是在编译时进行的,而是在函数调用时进行的,每调用一次函数重新给一次初值,相当于执行一次赋值语句
如果在定义局部变量时不赋初值的话:
对静态局部变量来说,编译时自动赋初值0(对数值型变量)或空字符 '\0'
(对字符变量)
对自动变量来说,它的值是一个不确定的值,这是由于每次函数调用结束后存储单元已释放,下次调用时又重新另外分配存储单元,而所分配的单元中的内容是不可知的
虽然静态局部变量在函数调用结束后仍然存在,但其他函数是不能引用它的。因为它是局部变量,只能被本函数引用,而不能被其他函数引用
如果函数中的变量只被引用而不改变值,则定义为静态局部变量(同时初始化)比较方便,以免每次调用时重新赋值
用静态存储要多占内存(长期占用不释放,而不能像动态存储那样一个存储单元可以先后为多个变量使用,节约内存)而且降低了程序的可读性,当调用次数多时往往弄不清楚静态局部变量的当前值是什么。因此,若非必要,不要多用静态局部变量
寄存器变量(register 变量)
一般情况下,变量(包括静态存储方式和动态存储方式)的值是存放在内存中的。当程序中用到哪一个变量的值时,由控制器发出指令将内存中该变量的值送到运算器中。经过运算器进行运算,如果需要存数,再从运算器将数据送到内存存放
如果有一些变量使用频繁,则为存取变量的值要花费不少时间,为提高执行效率,允许将局部变量的值存放在 CPU 中的寄存器中,需要用时直接从寄存器取出参加运算,不必再到内存中去存取。由于对寄存器的存取速度远高于对内存的存取速度,因此这样做可以提高执行效率,这总变量叫做寄存器变量,用关键字 register
作声明
register 变量类型 变量名
由于现在的计算机的速度越来越快,性能越来越高,优化的编译系统能够识别使用频繁的变量,从而自动的将这些变量放在寄存器中,而不需要程序设计者指定。因此,现在实际上用 register
声明变量的必要性不大
三种局部变量的存储位置是不同的:
- 自动变量存储在动态存储区
- 静态局部变量存储在静态存储区
- 寄存器存储在 CPU 的寄存器中
全局变量的存储类别
全局变量都是存放在静态存储区中的。因此它们的生存期是固定的,存在于程序的整个运行过程。一般来说,外部变量是在函数的外部定义的全局变量,它的作用域是从变量的定义处开始,到本程序文件的末尾。在此作用域内,全局变量可以为程序中各个函数所引用
在一个文件内扩展外部变量的作用域
如果外部变量不在文件的开头定义,其有效的作用范围只限于定义处到文件结束。在定义点之前的函数不能引用该外部变量。如果由于某种考虑,在定义点之前的函数需要引用该外部变量,则应该在引用之前用关键字 extern
对该变量作“外部变量声明”,表示把该外部变量的作用域扩展到此位置。有了此声明,就可以从“声明”处起,合法的使用该外部变量
extern 变量类型 变量名
或extern 变量名
提倡将外部变量的定义放在引用它的所有函数之前,这样子可以避免在函数中多加一个 extern
声明
用 extern
声明外部变量时,类型名可以写也可以不写,因为它不是定义变量,可以不指定类型,只虚写出外部变量名即可
一个C程序可以由一个或多个源程序文件组成。如果程序只由一个源文件组成,此就是使用外部变量的方法
将外部变量的作用域扩展到其他文件
一个C程序可以由一个或多个源文件组成。如果一个程序包含两个文件,在两个文件中都要用到同一个外部变量 Num ,不能分别在两个文件中各自定义一个外部变量 Num ,否则在进行程序的连接时会出现“重复定义”的错误。正确的做法是:在任一文件中定义外部变量 Num ,而在另一个文件中用 extern
对 Num 作“外部变量声明”,即“extern Num”。在编译和连接时,系统会由此知道 Num 有“外部连接”,可以从别处找到已定义的外部变量 Num,并将在另一个文件中定义的外部变量 Num 的作用域扩展到本文件,在本文件中可以合法的引用外部变量 Num
用这样方法扩展全局变量的作用域应十分慎重,因为在执行一个文件中的操作时,可能会改变该全局变量的值,会影响到另一个文件中全局变量的值,从而影响该文件中函数的执行结果
在编译时遇到 extern 时,先在本文件中找外部变量的定义,如果找到,就在本文件中扩展作用域;如果找不到,就在连接时从其他文件中找外部变量的定义。如果从其他文件中找到了,就将作用域扩展到本文件中;如果找不到,就报错
将外部变量的作用域限制在本文件中
有时在程序设计中希望某些外部变量只限于被本文件使用,而不能被其他文件引用,此时可以在定义外部变量时加上一个 static 声明
加上 static 声明、只能用于本文件的外部变量称为静态外部变量。在程序设计中,常由若干人分别完成各个模块,各人可以独立的在其设计的文件中使用相同的外部变量名而互不相干。只须在每个文件中定义外部变量时加上 static 即可,这就为程序的模块化、通用性提供方便。如果已确认其他文件不需要引用本文件中的外部变量,就可以对本文件中的外部变量都加上 static ,成为静态外部变量,以免被其他文件误用。这就相当于把本文件的外部变量对外界“屏蔽”起来,从其他文件的角度看,这个静态外部变量是“看不见,不能用”的。至于在各文件中在函数内定义的局部变量,本来就不能被函数外引用,更不能被其他文件引用,因此是安全的
不要误认为对外部变量加 static 声明后采取静态存储方式(存放在静态存储区中),而不加 static 的是采取动态存储(存储在动态存储区)。声明局部变量的存储类型和声明全局变量的存储类型的含义是不同的。对于局部变量来说,什么存储类型的作用是指定变量存储的区域(静态存储区或动态存储区)以及由此产生的生存期的问题,而对于全局变量来说,由于都是在编译时分配内存的,都存放在静态存储区,声明存储类型的作用是变量作用域的扩展问题
用 static 声明一个变量的作用:
- 对局部变量用 static 声明,把它分配在静态存储区,该变量在整个程序执行期间不释放,其所分配的空间始终存在
- 对全局变量用 static 声明,则该变量的作用域只限于本文件模块(即被声明的文件中)
用 auto,register 和 static 声明变量时,是在定义变量的基础上加上这些关键字,而不能单独使用
存储类别小结
对一个数据的定义,需要指定两种属性:数据类型和存储类别,分别使用两个关键字
可以用 extern 声明已定义的外部变量
从作用域角度分,有局部变量和全局变量,它们采用的存储类别如下:
从变量存在的时间(生存期)来区分,有动态存储和静态存储两种类型。静态存储是程序整个运行时间都存在,而动态存储则是调用函数时临时分配单元。具体如下:
从变量值存放的位置区分:
对一个变量的属性可以从两个方面分析,一是变量的作用域,一是变量值存在时间的长短,即生存期。前者是从空间的角度,后者是从时间的角度。二者有联系但不是同一回事
如果一个变量在某个文件或函数范围内是有效的,就称该范围为该变量的作用域,在此作用域内可以引用该变量,在专业书中称变量在次作用域内“可见”,这种性质称为变量的可见性
如果一个变量值在某一时刻是存在的,则认为这一时刻属于该变量的生存期,或称该变量在此时刻“存在”
变量存储类别 | 函数内 | 函数外 | ||
作用域 | 存在性 | 作用域 | 存在性 | |
自动变量和寄存器变量 | √ | √ | × | × |
静态局部变量 | √ | √ | × | √ |
静态外部变量 | √ | √ | √(只限本文件) | √ |
外部变量 | √ | √ | √ | √ |
自动变量和寄存器变量在函数内外的“可见性”和“存在性”一致,即离开函数后,值不能被引用,值也不存在
静态外部变量和外部变量在函数内外的“可见性”和“存在性”一致,即离开函数后,变量值仍存在,且可被引用
静态局部变量在函数内外的“可见性”和“存在性”不一致,离开函数后,变量值存在,但不能被引用
static 对局部变量和全局变量的作用不同:
- 对局部变量来说,它使变量由动态存储方式改变为静态存储方式
- 对全局变量来说,它使变量局部化(局部于本文件),但仍为静态存储方式
从作用域角度看,凡有 static 声明的,其作用域都是局限的,或者是局限于本函数内(静态局部变量),或者是局限于本文件内(静态外部变量)