C和指针 3.7 存储类型

缘起

《C和指针》 3.7节

分析

变量的存储类型也就是变量存在哪里? 它决定了变量的生命周期(何时创建、何时销毁). 变量的存储类型有静态存储区(static)、运行时堆栈(auto)、硬件寄存器(register)。 这三个地方存放的变量有不同的特征. 其中,静态存储区和运行时堆栈是在内存中, 寄存器不在内存中.

变量的默认存储位置取决于它声明的位置.

static

任何代码块之外声明的变量(也就是我们说的全局变量)存储于静态存储区.

静态存储区中的变量你无法为其指定其他的存储类型. 静态存储区的变量在程序运行之前就初始化(确切讲是程序被加载到内存中的时候就初始化, 不显式指定初始值的话,就是0,这个初始化过程并不占用程序执行时间,并且程序整个加载+运行只初始化一次),程序运行的整个过程它一直存在.

auto

代码块内部声明的变量默认存储类型是auto的,也就是存储在运行时堆栈中.程序执行到声明这种变量的代码块时,该变量才被创建.当程序执行流离开该代码块的时候,这些变量自行销毁. 如果该代码块被反复调用——例如一个函数被反复调用,这些变量每次都将重新创建. 而且每次这些变量在运行时堆栈中占据内存的位置可能与上一次相同,也可能与上一次不同(一般都不同). 即使相同了,你也不能保证这块内存以后不会另作他用(static存储类型的变量占据的内存就可以保证在程序加载到运行期间不会另作他用). 如果将代码块内部定义的变量使用static修饰的话(函数的入参不能用static修饰,因为它总要在运行时堆栈的),则就改变了它的存储区域——存到静态存储区去了(从而初始化只会执行一次(按照程序中写的值进行初始化, 缺省的话就是0)——每次方法被调用,不会重新初始化,而且在程序运行过程中一直存在). 但是并不意味着这个变量的作用域也变化了. 它依旧不能被代码块之外的代码访问.

register

register可用于auto变量的修饰. 提示编译器应该将他们存储在硬件的寄存器而不是内存中. 这种变量称为寄存器变量。通常寄存器变量的访问比内存中存储的变量高效. 但是C标准并未规定编译器一定要理睬register关键字(即编译器未必要产生”将此变量存储到硬件寄存器中去”这样的机器指令). 通常操作是——如果有太多auto变量被声明为register的话,则只会选取前几个存储在寄存器中,后面的都会按auto处理。或者说如果编译器对register变量有自己的一套优化策略的话,则编译器遇到register关键字就可以不生成”将此变量存储到硬件寄存器中去”这样的机器指令.

自然的,我们希望把使用频率最高的几个变量存储到寄存器中去. register变量的声明周期和auto一样,即创建和销毁的时机和auto一样. 但是寄存器变量创建和销毁的时候还要做一些额外的工作——在一个寄存器变量中的值复制到运行时堆栈,寄存器销毁的时候,再复制回来——保证寄存器的现场没有被破坏.

关于自动(auto)变量的初始化问题

前面说到了static变量的初始化是在程序加载进内存的时候进行的——既没有额外的指令,也不需要占用额外的程序执行时间. 但是自动变量的初始化要有更多的消耗. 因为程序链接的时候还无法判断自动变量的存储位置(静态变量可以). 事实上,函数的自动变量每次被调用占据的内存位置都可能不一样. 基于此,自动变量没有默认的初始化值. waitwaitwait,有人可能会问了, 那下面的代码

1
2
3
{
int a = 3; // auto变量a的"初始值"为3
}

这不是有初始值吗? 不然,因为说了,自动变量因为编译器无法预知程序运行时该自动变量的地址. 所以无法给a初始化的(所以一般自动变量的初始化值是一个随机的值,你只是声明一个自动变量a,然后debug发现它是一个随机的值.). 所以其实编译器的处理是在代码块开始的时候,插入一条隐含的赋值语句,即上述int a = 3并不是变量的定义,而是变量的声明+赋值语句.