变量声明顺序对代码空间的影响

在声明变量后,编译时会根据变量类型给变量分配不同的地址空间,具体变量的地址及分配规则我们不做过多描述,这里关注两个细节问题:1.变量声明顺序不同会不会影响代码空间大小。2.如果空间受到影响在使用变量地址取值时会不会出现问题。当变量不是结构体或联合体时,其实影响不是非常明显,但我们也进行了验证,结构体和联合体的问题我们下篇再说。
在程序编译后,受影响的储存空间有两种,一是FLASH里的存储空间,二是为变量分配运行程序时使用的RAM空间。
RO-data和RW-data存在flash中,上电先加载在RAM中,ZI-data(初值为0的变量和无初值的变量)不用存在flash中,上电直接加载在RAM中,在FLASH上存储规则与在RAM中开辟空间的原则应该没有太大区别,这里统一以在RAM中开辟空间的规则说明。我们先看关于空间分配的两个传言。
传言1:在32位MCU中,即使声明一个uint8_t,编译器最终也会分配四字节空间给这个变量,因此我们在程序中声明uint8_t并不会节约空间,因此尽情的申请uint32_t吧。
传言2:由于变量字节对齐的原因,如果声明两个变量,顺序为:
uint8_t test_u8;
uint32_t test_u32;
则变量将会占八个字节,如果声明顺序为:
uint32_t test_u32;
uint8_t test_u8;
则只会占用5个字节,因此在声明变量时多考虑一下顺序问题可以节省储存空间。
这两个传言互相矛盾,但实际上两个传言都不正确。
首先我们看传言2中的第一个声明,这样的声明中test_u8在内存中也占了4个字节,似乎符合传言1的说法,但实际上并不是。test_u8占四个字节与系统运行程序的取址方式有关,由于32位系统一次取32位数据,取址是按四字节对齐的,如果test_u32在程序中储存时横跨了两个四字节的地址,那么就需要两条指令来完成对这个test_u32数据的读取,因此在编译的时候,编译器会自动优化,将test_u32的起始地址放在一个完整四字节空间的首个字节的位置,前面位置自动填充PAD补齐,因此总共占8个字节并不是test_u8占了4个字节,如果本身test_u32的起始地址就在四字节对齐处,那就不会填充多余的PAD,比如:
uint8_t test_u8_arr[4];
uint32_t test_u32;
这样一个uint8_t的变量就还是占一个字节,总共数组加test_u32共占8个字节,而不会是20个字节。那么既然如此,如果在申请变量时像传言2所说注意申请时变量申请顺序是否可以起到节约空间的效果,结论是意义不大。因为整个工程不可能只在一处有变量声明,编译时将不同位置的变量、代码按照一定规则合并在一起放置时,也会遵循四个字节取址的原则,对于无法放在四字节起点的内容前填入PAD,因此开辟空间时填不填PAD,具体填多少PAD不仅与一处变量声明的顺序有关,还与各处变量汇总变量位置的排布、加载进RAM区域运行的代码大小有关,因此单纯考虑在声明变量时的声明顺序来节约空间的方式意义不大。
以下图举例:
有如下全局变量
定义方式一:
在这里插入图片描述
占12字节,由于test_u16起始地址不能被2整除,因此会填入一个PAD。
在这里插入图片描述
定义方式二:
在这里插入图片描述
占11字节,编译后的确可以看到在这一文件的输出文件中,变量占用内存为11个字节,比第一种声明方式少1个字节。
在这里插入图片描述
但是从链接完成的全体文件来看,第一次编译和第二次编译过后的文件总大小却没有变化,均为1132BYTE。
在这里插入图片描述
发现尽管main.o的文件大小变为了11个字节,但是在多个文件进行链接时,却多填入了一个PAD,使得总体文件大小没有发生变化,也就是说我们调整声明顺序所带来的空间优化却被编译器整合时浪费了,而这种整合是我们很难控制的。
总结 因此可以看到,改变变量的声明顺序会在一定程度上优化代码空间,但意义以及实际可操纵性不大,因此不必过多关注。在使用地址取值过程中,一般都是对一个数组进行连续取值,不会出现跨变量跨类型的情况,因此一般也不会出现取值风险。 下一篇主要对结构体和联合体变量中的空间分配进行总结,结构体受打包、对齐方式的影响在实际应用过程中成员变量地址会发生变化因此尤其需要注意。