数据的存储和排列顺序

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 个字节的空间。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

昵称 *