PS: 写在前面
字节对齐这玩意我一直没明白到底是,怎么对齐,今天在牛客上的一道题目,让我大概了解了,字节对齐

这文章拖了好久都没完善,最近整理文章才发现

# 理论

先来补充一些理论.

# 什么是字节对齐?

理论上计算机对于任何变量的访问都可以从任意位置开始,
然而实际上系统会对这些变量的存放地址有限制,
通常将变量首地址设为某个数 N 的倍数,这就是字节对齐

简单来说呢就是,变量理论上可以从任何地址开始,但是因为某种原因,事实上变量的地址只能是 某个数 的 n 倍
至于什么原因这就涉及到 为什么要字节对齐

# 为什么要字节对齐

官话就是:

  1. 为了保证数据存取正常。由于硬件平台限制,并不是所有平台都是支持任何地址的内存存储数据
  2. 提高 CPU 对内存的访问速度

第一点我就不加细说了.
第二点,大概解释一下,CPU 对于 内存的操作粒度 一般是 N 的整数倍。对于大部分 ARM 架构的 CPU 都是不支持 非字节对齐的数据存储.

凡事都会有例外,所以对应的 x86 架构的 CPU, 是可以支持,非字节对齐的数据存储,但是对于非字节对齐的数据也不是一次读出 4 字节,而是采取多次读取对齐的内存,然后进行拼接
例如,每一次读取 4byte. 如果地址任意存储,存在一个变量 a , 大小为 2 byte, 位于 0x03~0x04 的上.

当 CPU 需要操作这个变量时,CPU 就要先从 0x00 处读一次数据,取最后一部分,再从 0x04 读取一次数据取出第一个字节,然后合并在一起组成变量 a
这样就会大大降低 CPU 访存的效率.

image

# 字节对齐的规则

下面的结论均为系统默认对齐规则下进行的:

struct stu1 {
    char a[18];
    double b;
    char c;
    int d;
    short e;
};

image

# 结构体中间

各结构体的起始地址按照各个类型变量默认规则进行摆放

char 类型变量除外,char 类型变量一般遵循 2 的倍数地址开始存储.

# 结构体最后

以结构体内最大的变量类型为准.

如果是像 int 类型是 4 个字节的,并且结构体的结尾地址不满足 4 的倍数,则需向离最近的 4 的倍数地址进行补齐;

如果是像 double 类型是 8 个字节,并且结构体的结尾地址不满足 8 的倍数,则需向离最近的 8 的倍数地址补齐;

以此类推……

# 结构体嵌套

子结构体的成员变量起始地址要视子结构体中最大变量类型决定

比如 struct a 含有 struct bb 里有 char,int,double 等元素,那 b 应该从 8 的整数倍开始存储

struct stu2 {
    char x;
    int y;
    double z;
    char v[6];
};
struct stu1 {
    union u1 {
        int a1;
        char a2[5];
    }a;
    struct stu2 b;
    int c;
};

image

# 数组成员

比如 char a[5] ,它的对齐方式和连续写 5 个 char 类型变量是一样的,也就是说它还是按一个字节对齐.

# 含联合体(union)成员

取联合体中最大类型的整数倍地址开始存储

# 思考:为什么 CPU 不从 3~4 直接读取变量 a

当我看到网上的 对字节对齐的解释的时候,说到上面第二点就不说了。我相信肯定不止我一个人想过这样的问题
为什么不从 x 开始 往后读 N 个字节?

其实这取决于 CPU 是否支持非对齐存储,如果处理可以支持不对齐读取,那么对不对齐并不会影响到数据的正确性。但是,还是会影响到处理器将数据读入内粗的速度

对齐访问允许处理器高效地使用其数据总线,对齐访问使得内存子系统可以更有效地工作,减少缓存行的分裂和合并.

不支持非对齐访问可以简化 CPU 的设计,减少硬件复杂性和制造成本。尤其是对微处理器来说

# 参考文献

[1] 【C/C++】内存对齐


大道五十,天衍四十九,人遁其一!