断袖问情字节对齐主要是为了提高内存的访问效率,比如intel 32位cpu,每个总线周期都是从偶地址开始读取32位的内存数据,如果数据存放地址不是从偶数开始,则可能出现需要两个总线周期才能读取到想要的数据,因此需要在内存中存放数据时进行对齐。
通常我们说字节对齐很多时候都是说struct结构体的内存对齐,比如下面的结构体:
在32位机器上char 占1个字节,int 占4个字节,short占2个字节,一共占用7个字节.但是实际真的是这样吗?
测试输出的结果是A: 12, 比计算的7多了5个字节。这个就是因为编译器在编译的时候进行了内存对齐导致的。
其实这里有点不严谨,编译器在编译的时候是可以指定对齐大小的,实际使用的有效对齐其实是取指定大小和自身大小的最小值,一般默认的对齐大小是4。
再回到上面的例子,如果默认的对齐大小是4,结构体a的起始地址为0x0000,能够被最宽的数据成员大小(这里是int, 大小为4,有效对齐大小也是4)整除,姑char a的从0x0000开始存放占用一个字节即0x0000~0x0001,然后是int b,其大小为4,故要满足2,需要从0x0004开始,所以在char a后填充三个字节,因此a对齐后占用的空间是0x0000~0x0003,b占用的空间是0x0004~0x0007, 然后是short c其大小是2,故从0x0008开始占用两个字节,即0x0008~0x0009。 此时整个结构体占用的空间是0x0000~0x0009, 占用10个字节,10%4 != 0, 不满足第三个原则,所以需要在后面补充两个字节,即最后内存对齐后占用的空间是0x0000~0x000B,一共12个字节。
前面的各位的方法很好,很传神,不适合速成,想要速成还得看我这种小屁孩写的
30秒....敌人还有三十秒到达战场...来看看下面的Ex(不是前任...!)
第一地址肯定存放a是4Byte地址,第二地址,b要1Byte的地址,来欢迎公式一登场: 4 == 1*N (N等于正整数) 答是!地址现在为5Byte,下一个c要1Byte的地址同上,所以,就是6Byte。来欢迎公式二登场,在这个E1中最大的字节是4,而我们的地址字节是6,4的整数倍不是6,所以,要加2Byte(总地址),So,整个字节为8!
每个特定平台上的编译器都有自己的默认“对齐系数”。可以通过预编译命令#pragma pack(n)
一个最小存储单位为 8 字节的内存来说。访问地址1, 大小为 4 字节的数据。只需读取地址 0 的 8 字节的数据。然后在出口处移位下就行。因为只需在出口处做一次处理,所以可以不计成本进行移位优化,但这种优化只能在部件内部或者部件组内部进行。不同组件的交互部分对“对齐”还是有不同的看法的。又因为 RISC CPU 的设计,大多精简指令集的指令长就是字长。而指令还需区分取立即数和各种 action, 一字长的指令无法全部用来表示地址空间。综上,大多 RISC CPU 强制地址对齐,地址的低位脑补成 0。顺便也减少了地址线的宽度。
访问内存的速度是非常非常非常……慢的。再加上 CPU 及其指令设计的限制。在这种艰苦条件下,我们必须无所不用其极地减少随机内存访问次数:
以一个字长为 4 字节的 RISC CPU 来进行讨论。单位为字节。 此时,如果我们需要访问地址为 2 的大小为 1 字长也就是 4 字节的数据,也就是 2-5 的数据. 地址 2 mod 大小 4 = 2 不为 0. 这是未对齐的访问。(地址线以二进制表示。最后两位空置,所以始终为逻辑 0)我们需要将地址线, 然后将地址线, 然后再合成所需数据。共两次访问。如果要访问地址为 0(00) 或者 1(00) 的大小为 1 字长的数据。则一次访问即可。 这就是地址不对齐导致访问变慢的来历了。
那么,问题来了。既然如此,我也可以访问地址 1, 大小为 2 字节的数据啊。也就是 1-2 的数据。将地址线) 读出数据,然后在寄存器内左移 1 字节,再右移 2 字节就行了啊。 这时候 1 mod 2= 1, 不为 0, 但需要访问内存的次数还是一次。 但世界上有许多地方,那儿的 CPU 字长只有 2 字节。当数据到那些地方去旅行时。那儿的 CPU 访问地址为 1, 大小为 2 字节的数据的时候还是需要两次的。
那么,问题又来了。字长为 4 字节的 CPU, 访问 8 字节长度的数据,这个数据反正始终都要读两次,那么它不对齐也是可以的呀。只要他的地址 mod 4 为 0 就可以了。 但世界上还有些地方,那儿的 CPU 字长是 8 字节的,当数据到那些地方去旅行时。那儿的 CPU 访问大小为 8 字节的数据,若其地址 mod 8 为 0 时,只要读一次就够了。此时读两次就是一种浪费了。
, 而 28 mod 8= 4 不为 0. 如果 hi 的大小为其中最大单元的整数倍也就是 8 * 3= 24 的话。那么 第二个 hi 的 us 字段的地址是 24+8= 32. 而 32 mod 8= 0, 对齐了。所以,最后我们还需要 padding 4 字节。 在这个不孤单的世界里,为了同一类数据能和谐相处。所以我们郑重做出约定:
背书式:各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数 各成员变量在存放的时候根据在结构中出现的顺序依次申请空间 同时按照上面的对齐方式调整位置 空缺的字节自动填充 同时为了确保结构的大小为结构的字节边界数(即该结构中占用最大的空间的类型的字节数)的倍数,所以在为最后一个成员变量申请空间后 还会根据需要自动填充空缺的字节
|