数据的存储和排列顺序
1. 数据的存储和排列顺序
现代计算机基本上都采用字节编址方式,即对存储空间的存储单元进行编号时,每个地址编号中存放一个字节。计算机中许多类型的数据由多个字节组成,例如,int 和 float 型数据占用 4 个字节,double 型数据占用 8 个字节等,而程序中对每个数据只给定一个地址。
在所有计算机中,多字节数据都被存放在连续的字节序列中。根据数据中各字节在连续字节序列中的排列顺序的不同,可有两种排列方式:大端(big endian)和小端(little endian)。例如,在一个按字节编址的计算机中,假定 int 型变量 i 的地址为 0800H,i 的机器数为 01 23 45 67H,这 4 个字节 01H、23H、45H、67H 的存储地址如下:
变量的地址是最小地址。LSB 表示最低有效字节(01H),MSB 表示最高有效字节(67H)。
大端方式:将数据的最高有效字节存放在低地址单元中,将最低有效字节存放在高地址单元中,即变量的地址就是 MSB 所在的地址。
小端方式:将数据的最高有效字节存放在高地址单元中,将最低有效字节存放在低地址单元中,即数据的地址就是 LSB 所在的地址。
2. 内存对齐
CPU 通过地址总线来访问内存,一次能处理几个字节的数据,就命令地址总线读取几个字节的数据。32 位的 CPU 一次可以处理 4 个字节的数据,那么每次就从内存读取 4 个字节的数据,少了浪费主频,多了没用。
以 32 位的 CPU 为例,实际寻址的步长为 4 个字节,也就是只对编号为 4 的倍数的内存寻址。对于程序来说,一个变量最好位于一个寻址步长的范围内,这样一次就可以读取到变量的值;如果跨步长存储,就需要读取两次,然后再拼接数据,效率显然降低了。
将一个数据尽量放在一个步长之内,避免跨步长存储,这称为内存对齐。为了提高寻址效率,编译器会自动进行内存对齐,在 32 位编译模式下,默认以 4 字节对齐,请看下面的代码:
#include <stdio.h>
#include <stdlib.h>
struct {
int a;
char b;
int c;
} t = {10, 'C', 20};
int main()
{
printf("length:%d\n", sizeof(t));
printf("&a: %X\n&b: %X\n&c: %X\n", &t.a, &t.b, &t.c);
return 0;
}
在 32 位编译模式下的运行结果:
length:12
&a: 402008
&b: 40200C
&c: 402010
如果不考虑内存对齐,结构体变量 t 所占内存应该为 4+1+4 = 9 个字节。考虑到内存对齐,虽然成员 b 只占用 1 个字节,但它所在的寻址步长内还剩下 3 个字节的空间,放不下一个 int 型的变量了,所以要把成员 c 放到下一个寻址步长。剩下的这 3 个字节,作为内存填充浪费掉了。请看下图:
编译器之所以要内存对齐,是为了更加高效的存取成员 c,而代价就是浪费了 3 个字节的空间。