数据对齐(Data Alignment)

本文介绍了C语言中结构体的工作原理,强调了数据对齐的重要性。通过对不同类型字段在结构体中的存储方式进行分析,揭示了编译器如何在内存中插入间隙以满足对齐约束,从而提高内存访问效率。并提供了结构体大小和字段偏移的计算实例,加深对数据对齐概念的理解。
摘要由CSDN通过智能技术生成

结构体

C struct 声明创建一种数据类型,以组合不同类型的对象到单个的对象中。结构体的不同组件通过名字进行引用。结构体的实现与数组类似,其组件存储在一块连续的内存区域。编译器负责维护每个结构体类型的信息,指示每个字段的字节偏移。编译器使用这些偏移作为内存引用指令中的位移以生成对结构体元素的引用。

假设有下述结构体声明:

struct rec {
	int i;
	int j;
	int a[3];
	int *p;
};

该结构体包含 4 个字段:2 个 4 字节的 int,一个由 3 个 4 字节 int 组成的数组,以及一个 4 字节的整型指针,总计 24 个字节:
在这里插入图片描述
图顶部的数字给出字段距结构体起始点的字节偏移。

为了访问结构体的字段,编译器生成的代码对结构体地址添加合适的偏移。例如,rec* 类型的变量 r 存储于寄存器 %edx。下述代码拷贝元素 r->i 到 r->j 中:

1 movl (%edx), %eax 			Get r->i
2 movl %eax, 4(%edx) 			Store in r->j

因为字段 i 的偏移是 0,该字段的地址就是 r 的值。为了存储到字段 j 中,代码向 r 的地址添加了 4 字节的偏移。

数据对齐

很多计算机系统对基本数据类型的允许地址作出限制,要求某些类型对象的地址必须是值 K (通常是 2,4,8) 的倍数。这样的对齐约束 (alignment restrictions) 简化了在处理器内存系统间形成接口的硬件的设计。例如,假设处理器总是能从其地址是 8 的倍数的内存处抓取 8 个字节。如果我们能保证任何 double 都被对齐,其地址都是 8 的倍数,那么其值就能在一次内存操作中被读写。否则,我们可能需要两次内存访问,因为对象可能跨两个 8 字节的内存块。

IA32 硬件能否正常工作与数据的对齐无关。但是 Intel 建议数据被对齐以提示内存系统的性能。Linux 的对齐政策遵循 2 字节数据类型 (e.g.,short) 的地址必须是 2 的倍数更大的数据类型 (e.g.,int*,float,和 double) 的地址必须是 4 的倍数。注意这一需求意味着 short 类型的对象其地址的最低有效位必须等于 0。类似的,对象类型 int,或任何指针,必须位于其低序的 2 位值均为 0 的地址上。

为了保证对齐,每个数据类型都以下述方法进行组织和分配:类型中的每个对象都满足它的对齐约束。编译器指明其期望的对齐方式。例如,

.align 4

这保证了数据将以值为 4 的倍数的地址处开始。

分配内存的库例程,例如 malloc,返回的指针必须满足其运行所在的机器上最严格的对齐要求,通常是 4 或 8。对于涉及结构体的代码,编译器需要在字段分配中插入间隙,以保证结构体元素满足它的对齐要求。结构体自己的起始地址也由一些对齐要求。

例如,假设有下述结构体声明:

struct S1 {
	int i;
	char c;
	int j;
};

假设编译器使用最小的 9 个字节分配,如下图:
在这里插入图片描述
对于字段 i 和字段 j 来说都没有满足 4 字节的对齐要求。相反,编译器在字段 c 和字段 j 之间插入一个 3 字节的间隙 (蓝色阴影部分):
在这里插入图片描述
结果,j 的偏移变为 8,整个结构体大小变为 12 字节。进一步而言,编译器必须保证 S1* 的指针 p 满足 4 字节对齐。假设指针 p 的值为 Xp,则 Xp 必须是 4 的倍数。这保证了 p->i (地址 Xp) 和 p->j (地址 Xp + 8) 都满足 4 字节的对齐要求。

此外,编译器可能需要在结构体尾部添加填充,这样结构体数组中的元素将满足对齐要求。例如,假设有下述结构体声明:

struct S2 {
	int i;
	int j;
	char c;
};

如果我们以 9 字节打包该结构体,对于字段 i,和 j 依然能满足对齐要求,只要结构体的起始地址能满足 4 字节对齐的要求。然后,假设有下述声明:

struct S2 d[4];

使用 9 字节的分配,不可能满足 d 中每个元素的对齐要求,因为这些元素的地址将为 Xd,Xd + 9,Xd + 18,Xd + 27。相反,编译器为结构体 S2 分配 12 个字节,最后 3 个字节是被浪费的空间:
在这里插入图片描述
使用这种方式,元素的地址为 Xd,Xd + 12,Xd + 24, Xd + 36。只要 Xd 是 4 的倍数,所有的元素都将满足对齐要求。

练习

A. struct P1 { int i; char c; int j; char d; };
B. struct P2 { int i; char c; char d; int j; };
C. struct P3 { short w[3]; char c[3] };
D. struct P4 { short w[3]; char *c[3] };
E. struct P5 { struct P1 a[2]; struct P2 *p };

上述结构体的大小和各字段的地址偏移是多少?
答:
在 x64 平台下,

  • P1 的大小是 16 字节,各字段偏移依序分别是 i : 0,c : 4,j : 8,d : 12;
  • P2 的大小是 12 字节,各字段偏移依序分别是 i : 0,c : 4,d : 5,j : 8;
  • P3 的大小是 10 字节,各字段偏移依序分别是 w[0] : 0,w[1] : 2,w[2] : 4,c[0] : 6,c[1] : 7,c[2] : 8;
  • P4 的大小是 32 字节,各字段偏移依序分别是 w[0] : 0,w[1] : 2,w[2] : 4,c[0] : 8,c[1] : 16,c[2] : 24;
  • P5 的大小是 40 字节,各字段偏移依序分别是 a[0] : 0,a[1] : 16,p : 32;

总结

  • 结构体是一段连续的内存区域,其成员通过相对结构体起始地址的偏移进行访问。
  • 现代处理器的内存子系统要求以字长为粒度和对齐要求访问内存,即在字长倍数的地址上每次读写字长字节长度的数据。
  • Linux 遵循 1 字节数据类型 (char) 无对齐要求,2 字节基本数据类型 (short) 位于值为 2 的倍数的地址处,更大的基本数据类型 (int,long等) 位于值为 4 的倍数的地址处。
  • 结构体会在字段尾部添加填充以使下一字段或结构体本身满足对齐要求。
  • 数据对齐的唯一要求是满足最小次数的内存操作,小于字长为一次。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值