内存对齐之实践出真知

160 阅读3分钟

学习内存对齐的时候,我有很多问题,在 https://juejin.cn/post/6844904202045505549

基本上是对 内存对齐的原因、实现 都产生了怀疑。

内存对齐的原因,因为知识水平所限+大家对这个东西都不感兴趣,所以我没有找到解答我问题的书或者文章。

这篇文章只是讲内存对齐的实现的理解。

内存对齐的实现

我原来陷在一个误区中,我以为需要有个地方存储对齐系数,其实根本不需要。

c语言对结构体某个元素的访问只是结构体的地址加上偏移就可以了,而这个偏移是固定的,而不是程序运行时动态获取的。

struct A {
    void *p;
    bool a;
};

- (void)viewDidLoad {
    [super viewDidLoad];
    struct A a = {NULL, true};
    struct A b = {&a, true};
    b.a = false;
}

汇编结果(我只列出了重点的初始化结构体和赋值的部分):

    0x10b20e203 <+51>: movq   0xde16(%rip), %rax
    0x10b20e20a <+58>: movq   %rax, -0x30(%rbp)
    0x10b20e20e <+62>: movq   0xde13(%rip), %rax
    0x10b20e215 <+69>: movq   %rax, -0x28(%rbp)
    0x10b20e219 <+73>: leaq   -0x30(%rbp), %rax
    0x10b20e21d <+77>: movq   %rax, -0x40(%rbp)
    0x10b20e221 <+81>: movb   $0x1, -0x38(%rbp)
    0x10b20e225 <+85>: movb   $0x0, -0x38(%rbp)
    

可以看出,在汇编层面已经没有什么结构体不结构体了,就只是某个地址放了某个数据,和以下的代码没有区别:

    void *ap = NULL;
    bool aa = true;
    void *bp = NULL;
    bool ba = true;
    ba = false;

所以也就不需要特别在某个地方存放某结构体或者某文件的对齐系数,完全是我的胡言乱语😄

设置对齐系数

**//#pragma pack(1)
struct A {
    void *p;
    bool a;
    void *p2;
};

- (void)viewDidLoad {
    [super viewDidLoad];
    struct A a = {NULL, true, NULL};
    struct A b = {&a, true, &a};
}

在设置对齐系数和不设置对齐系数的情况下,观察结构体b的内存空间:

设置对齐系数为1时,结构体b的内存空间:

设置对齐系数为1时,结构体b的内存空间

不设置对齐系数的情况下,结构体b的内存空间:

可见,在设置对齐系数为1时,结构体的第二个数据成员只占据了1个字节

在设置对齐系数为1时,sizeof(struct A)的结果是17,而不是24,也佐证了上面的结果

但是查看结构体a, b的地址:

(lldb) po &a
0x00007ffeef45f508

(lldb) po &b
0x00007ffeef45f4f0

实际上它们仍然占据24个字节。 可见sizeof(struct A)只计算了内对齐,没有把外对齐算在其中。

而结构体的外对齐也不像其他文章中说的那样:

结构体的整体对齐规则:
在数据成员按照上述第一步完成各自对齐之后,结构体本身也要进行对齐。
对齐会将结构体的大小调整为
(#pragma pack(指定的数n) 与 结构体中的最大长度的数据成员中较小那个
的整数倍,不够的补齐。

可能是苹果在原来规则之外另外添加的。不过这个已经不重要了