请注意,不同数据类型的大小可能会因编译器和计算机体系结构而异。sizeof()运算符将是查找数据类型的实际大小的最佳方法。
结构的内存布局
现在,让我们检查一下结构的内存布局。考虑为32位计算机编译以下结构:
- struct Test2{
- uint8_t c;
- uint32_t d;
- uint8_t e;
- uint16_t f;
- } MyStruct;
复制代码
我们知道将分配四个内存位置以在结构中存储成员,并且内存位置的顺序将与声明成员的顺序匹配。第一个成员是一个字节的变量,可以存储在任何地址。因此,第一个可用的存储位置将分配给该变量。假定如图5所示,编译器将地址0分配给该变量。下一个成员是四字节数据类型,只能存储在4的倍数的地址上。第一个可用的存储位置是地址4。但是,这需要保留地址1、2和3未被使用。如您所见,数据对齐要求导致了内存布局中一些浪费的空间(或填充)。
下一个成员是e,它是一个一字节的变量。可以将第一个可用的存储位置(图5中的地址8)分配给该变量。接下来,我们到达f,它是一个两个字节的变量。可以将其存储在可被2整除的地址中。第一个可用空间是地址10。如您所见,将出现更多的填充以满足数据对齐要求。
图5
我们希望该结构占用8个字节,但实际上需要12个字节。有趣的是,如果我们了解数据对齐要求,则可以重新排列结构中成员的顺序,并提高内存使用效率。例如,让我们重写下面的结构,其中成员从最大到最小排列。
- struct Test2 {
-
- uint32_t d;
- uint16_t f;
- uint8_t c;
- uint8_t e;
-
- } MyStruct;
复制代码
在32位计算机上,上述结构的内存布局可能类似于图6中所示的布局。
图6
第一个结构需要12个字节,而新结构仅需要8个字节。这是一项重大改进,尤其是在内存受限的嵌入式处理器的情况下。
另外,请注意,结构的最后一个成员之后可能会有一些填充字节。结构的总大小必须被其最大成员的大小整除。考虑以下结构:
- struct Test3 {
-
- uint32_t c;
- uint8_t d;
-
- } MyStruct2;
复制代码
在这种情况下,内存布局将如图7所示。您可以看到,在内存布局的末尾添加了三个填充字节,以将结构的大小增加到8个字节。这将使结构大小可被结构中较大成员(c成员,这是一个四字节的变量)的大小整除。
图7
概要
- 处理器通常以大于一个字节的块的形式访问内存。这样可以提高系统效率。
- 处理器访问内存时使用的数据大小是处理器的内存访问粒度。
- 处理器可能被限制为仅在某些边界(例如,在四字节边界)访问存储器。
- 存在此内存访问限制是因为对地址进行某些假设可以简化硬件设计。
- 通常,任何K字节的C数据类型都必须具有K的倍数的地址。此类限制简化了处理器与内存系统之间接口硬件的设计。
- 数据对齐要求导致内存布局中的某些空间浪费(或填充)。
- 结构的最后一个成员之后可能会有一些填充字节。结构的总大小必须被其最大成员的大小整除。