C语言变量的存储类别和生存期
我们知道,变量是有数据类型的,用以说明它占用多大的内存空间,可以进行什么样的操作。
除了数据类型,变量还有一个属性,称为“存储类别”。存储类别就是变量在内存中的存放区域。在进程的地址空间中,常量区、全局数据区和栈区可以用来存放变量的值。
- 常量区和全局数据区的内存在程序启动时就已经由操作系统分配好,占用的空间固定,程序运行期间不再改变,程序运行结束后才由操作系统释放;它可以存放全局变量、静态变量、一般常量和字符串常量。
- 栈区的内存在程序运行期间由系统根据需要来分配(使用到变量才分配内存;如果定义了变量但没有执行到该代码,也不会分配内存),占用的空间实时改变,使用完毕后立即释放,不必等到程序运行结束;它可以存放局部变量、函数参数等。
我们可以通过C语言中的关键字来控制变量的存放区域。C语言共有 4 个关键字用来指明变量的存储类别:
- auto(自动的)
- static(静态的)
- register(寄存器的)
- extern(外部的)
知道了变量的存储类别,就可以知道变量的生存期。通俗地讲,生存期指的是在程序运行过程中,变量从创建到销毁的一段时间,生存期的长短取决于变量的存储类别,也就是它所在的内存区域。
本节我们只讲解 auto、static 和 register 三个关键字,extern将在模块化开发中讲解。
auto 变量
auto 是自动或默认的意思,很少用到,因为所有的变量默认就是 auto 的。也就是说,定义变量时加不加 auto 都一样,所以一般把它省略,不必多次一举。
例如:
1 | int n = 10; |
与
1 | auto int n = 10; |
的效果完全一样。
static 变量
static 声明的变量称为静态变量,不管它是全局的还是局部的,都存储在静态数据区(全局变量本来就存储在静态数据区,即使不加 static)。
- 静态数据区的数据在程序启动时就会初始化,直到程序运行结束;
- 对于代码块中的静态局部变量,即使代码块执行结束,也不会销毁。
注意:静态数据区的变量只能初始化(定义)一次,以后只能改变它的值,不能再被初始化,即使有这样的语句,也无效。
请看下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #include <stdio.h> int sum( int n) { // 也可以不赋初值 0,静态数据区的变量默认初始化为 0 static int result = 0; result += n; return result; } int main() { int result, i; for (i = 1; i <= 100; i++) { result = sum(i); } printf ( "1+2+3+...+99+100 = %d\n" , result); return 0; } |
我们在 sum() 中定义了一个静态局部变量 result,它存储在静态数据区,sum() 函数执行结束也不会销毁,下次调用继续有效。静态数据区的变量只能初始化一次,第一次调用 sum() 时已经对 result 进行了初始化,所以再次调用时就不会初始化了,也就是说 static int result = 0; 语句无效。
静态局部变量虽然存储在静态数据区,但是它的作用域仅限于定义它的代码块,sum() 中的 result 在函数外无效,与 main() 中的 result 不冲突,除了变量名一样,没有任何关系。
register 变量
一般情况下,变量的值是存储在内存中的,CPU 每次使用数据都要从内存中读取。如果有一些变量使用非常频繁,从内存中读取就会消耗很多时间,例如 for 循环中的增量控制:
1 2 3 4 | int i; for (i=0; i<1000; i++){ // Some Code } |
执行这段代码,CPU 为了获得 i,会读取 1000 次内存。
为了解决这个问题,可以将使用频繁的变量放在CPU的通用寄存器中,这样使用该变量时就不必访问内存,直接从寄存器中读取,大大提高程序的运行效率。不过寄存器的数量是有限的,通常是把使用最频繁的变量定义为 register 的。
来看一个计算 π 的近似值的例子,求解的一个近似公式如下:
为了提高精度,循环的次数越多越好,可以将循环的增量控制定义为寄存器变量,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #include <stdio.h> int main() { register int i = 0; // 寄存器变量 double sign = 1.0, res = 0, ad = 1.0; for (i=1; i<=100000000; i++) { res += ad; sign=-sign; ad=sign/(2*i+1); } res *= 4; printf ( "pi is %f" , res); return 0; } |
运行结果:
关于寄存器变量有以下事项需要注意:
- 为寄存器变量分配寄存器是动态完成的,因此,只有局部变量和形式参数才能定义为寄存器变量。
- 局部静态变量不能定义为寄存器变量,因为一个变量只能声明为一种存储类别。
- 寄存器的长度一般和机器的字长一致,只有较短的类型如 int、char、short 等才适合定义为寄存器变量,诸如 double 等较大的类型,不推荐将其定义为寄存器类型。
- CPU的寄存器数目有限,即使定义了寄存器变量,编译器可能并不真正为其分配寄存器,而是将其当做普通的auto变量来对待,为其分配栈内存。当然,有些优秀的编译器,能自动识别使用频繁的变量,如循环控制变量等,在有可用的寄存器时,即使没有使用 register 关键字,也自动为其分配寄存器,无须由程序员来指定。(个人:auto 作用于全局变量时,变量存储在全局数据区,作用于局部变量是,变量存储在栈区)
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10亿数据,如何做迁移?
· 推荐几款开源且免费的 .NET MAUI 组件库
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· c# 半导体/led行业 晶圆片WaferMap实现 map图实现入门篇
· 易语言 —— 开山篇
2021-05-02 140. Word Break II
2021-05-02 139. Word Break
2021-05-02 58. 最后一个单词的长度
2021-05-02 126. Word Ladder II
2021-05-02 127. 单词接龙
2021-05-02 49. 字母异位词分组
2021-05-02 242. 有效的字母异位词