C语言字节对齐

  • 格式:pdf
  • 大小:262.59 KB
  • 文档页数:22

C语言字节对齐

字节对齐的由来

程序在运行时会将数据临时存放在内存中,芯片内核需要对这些数据进行计算,不断的

读取内存以获得数据,并将计算结果写入内存。计算机体系经过若干年的发展,最终确定了

以8bits作为其基本的存储单元——byte(字节),这是每个地址所对应的最小访问单元,

在C语言中对应一个char型的变量。

下图为芯片内核访问内存的示意图。芯片内核通过控制总线控制内存的动作,通过地址

总线告知内存地址,数据总线上出现交互的数据。

图1访问内存示意图

假设上图是8位机的示意图,那么数据总线的宽度是8bits,由8根数据线组成,这样

芯片内核与内存之间一次就可以同时交换8个bits的数据,正好是一个字节。图中右侧的

每个小格子代表一个存储地址,对应一个字节。

下面通过一段C语言代码来具体看看芯片内核与内存之间的数据交互过程。

chardata[2];data[0]=2;data[1]=data[0]+1;

第一行代码定义了2个字节的数组data。假设data数组被编译到地址0x100,那么

data[0]这个字节就被存储在地址为0x100的内存空间,data[1]这个字节就被存储在地址为

0x101的内存空间。

第二行对应的硬件动作是将数据2存入到data[0]中,也就是将数据2存入到内存中的

0x100地址,执行这条语句时,芯片内核对控制总线、地址总线和数据总线进行操作,控制

总线上出现写信号,地址总线上出现数据0x100,数据总线上出现数据0x02。此时内存就

知道需要将数据2写入到地址0x100中,完成一次写操作。

第三行先读出data[0]中的数据,芯片内核将控制总线置为读信号,将地址总线置为

0x100,此时,内存就会从其内部取出0x100地址中的数据,也就是数据2,2将出现在数

据总线上,此时芯片内核就会通过数据总线读取到data[0]中的数据了。接下来芯片内核计

算2+1=3,需要将数字3写入到data[1]中,芯片内核将控制总线置为写信号,将地址总线

置为0x101,将数据总线置为3,内存接收到这些信号后,就会将数据3存入到其内部0x101

地址中,完成本次操作。内存内部细节

数据总线地址总线芯片内核内存230x100控制总线从上述介绍的过程可以看出,芯片内核与存储芯片之间每次操作可以传递1个字节的

数据,如果要传递多个字节的数据就需要重复这个过程,这受限于数据总线的宽度。

计算机技术在不断的发展,在8bits数据总线之后又相继出现了16bits、32bits乃至

64bits数据总线,它们分别对应于我们所谓的8位机、16位机、32位机以及64位机。对

于16位机一次可以交互2个字节的数据,32位机一次可以交互4个字节的数据,64位机

一次可以交互8个字节的数据,可以看出总线的带宽增加了,速度成倍提高。

以32位机为例,我们在访问0地址时,可以一次访问4个字节的数据,这4个字节的

数据占用了4个内存地址,也就是说访问0地址时同时可以访问0、1、2、3这4个地址,

访问4地址时可以同时访问4、5、6、7这4个地址。我们不难得出这样的结论:在地址总

线上只要出一个地址,就可以连同访问这个地址及其后面的3个地址中的数据,这4个地

址正好可以组成一个32bits的数据,通过访问数据总线一次即可获得,而对这个地址的要

求就是:需要4字节对齐(对于64位机则需要8字节对齐)。在芯片设计时遵循了这个要

求,地址总线上只需要出现0、4、8……这样4的整数倍的地址就可以同时访问连续4个字

节的内存空间,这就是字节对齐的根源——是由硬件决定的!为了配合硬件的4字节对齐

访问,软件的编译器链接器也对软件做了限制,需要4字节对齐访问。有关计算机的设计五花八门,上述有关控制总线、地址总线、数据总线的介绍只是原理性的介绍,不同芯片在具体实现时会有所不同。

字节对齐规则

我们在写代码时一般并不会指定变量存放在内存中的地址,这是由编译器链接器决定

的,而编译器链接器则遵循了4字节对齐的原则,以32位机为例,其规则是1字节长度的

变量可以被编译链接到任何地址,2字节长度类型的变量被编译链接到2的整数倍的地址,

4字节长度类型的变量被编译链接到4的整数倍的地址。因此,取signed/unsignedchar

类型变量的地址,它可以是任意地址。取signed/unsignedshortint类型变量的地址,它一

定是2的整数倍。取signed/unsignedint,signed/unsignedlong类型变量的地址,它一定

是4的整数倍。

C语言的结构体类型由多种基本类型组成,比较利于讨论字节对齐的问题,下面我们将

以结构体为例讲解字节对齐规则。以下例子除特殊说明外,均是在X8632位CPU,VC2010

环境下测试。

例1:typedefstructexample1{chara;}EXAMPLE1;

结构体EXAMPLE1比较简单,它其实就是一个char型,它的长度sizeof(EXAMPLE1)

为1。

例2:typedefstructexample2{chara;shortb;}EXAMPLE2;结构体EXAMPLE2中包含了2个变量,其中char型a的长度为1,short型b的长度

为2,但结构体EXAMPLE2的整体长度sizeof(EXAMPLE2)却为4,而不是1+2=3,这种

现象就是字节对齐造成的。

为了方便观察结构体中变量相对结构体头的偏移地址,我们定义如下的宏:

#defineOFFSET(s,e)((unsignedint)(&((s*)0)->e))

其中s为结构体类型,e为结构体中的变量,OFFSET返回的就是结构体中的变量e

相对于结构体s的偏移地址。通过该结构就可以看出结构体在内存中的分布。

求得结构体EXAMPLE2的数据如下:sizeof(EXAMPLE2)4OFFSET(EXAMPLE2,a)0OFFSET(EXAMPLE2,b)2

画出结构体EXAMPLE2在内存中分布如下:

abb

其中每个格子代表一个字节,a和b之间灰色背景的格子是编译器为了字节对齐而保留

的一个字节空间。为什么会保留一个字节的空间呢,这是因为结构体的对齐长度必须是其内

部变量类型中最长的对齐长度,也就是说存放结构体的起始地址必须是其内部变量类型中最

长的对齐长度的整数倍。结构体EXAMPLE2中变量a的对齐长度是1,变量b的对齐长度

是2,因此EXAMPLE2存放的地址必须是2的整数倍。变量a可以存放在任何地址,因此

存放在EXAMPLE2开始的第一个字节,这个字节所在的地址是2的整数倍,接下来的字节

(灰色)所在的地址不是2的整数倍,而变量b又只能存放在2的整数倍地址,因此a和b

之间只好空出1个字节,这就使结构体EXAMPLE2的长度变为4了。

例3:typedefstructexample3{chara;shortb;intc;}EXAMPLE3;

在结构体EXAMPLE2的基础上再增加一个int变量c构造成结构体EXAMPLE3,按照

例2中介绍的方法分析一下结构体EXAMPLE3的长度。

EXAMPLE3中最长对齐长度的变量是c,4个字节,因此EXAMPLE3开始的地址必须

是4的整数倍。变量a是1个字节,存放在EXAMPLE3开始的第一个字节。变量b是2

个字节,需要在a之后空出1个字节,才能存放在2字节对齐的地址。变量c是4个字节,

需要存放在4字节对齐的地址,前面的变量a、保留字节和变量b之后已经是4字节对齐的

地址了,因此变量c可以直接存放在变量b之后。

按照上面的分析,我们可以画出EXAMPLE3在内存中的分布示意图:abbcccc

可以看到EXAMPLE3占有8个字节。我们再使用sizeof和OFFSET计算EXAMPLE3

的数据进行验证,如下:

sizeof(EXAMPLE3)8OFFSET(EXAMPLE3,a)0OFFSET(EXAMPLE3,b)2OFFSET(EXAMPLE3,c)4

例4:typedefstructexample4{chara;charb;shortc;intd;}EXAMPLE4;

在结构体EXAMPLE3的基础上再增加一个char的变量构造成结构体EXAMPLE4,

EXAMPLE4比EXAMPLE3多了一个char型变量,那么EXAMPLE4是否会比EXAMPLE3

长1个字节?

EXAMPLE4中最长的对齐长度的变量是d,4个字节,因此EXAMPLE4开始的地址必

须是4的整数倍。变量a是1个字节,存放在EXAMPLE4开始的第一个字节。变量b是1

个字节,对字节对齐没有要求,直接存放在a后面。变量c是2个字节,在a、b之后已经

是2字节对齐的地址了,因此c可以直接存放在b之后,对齐到2个字节。变量d是4个

字节,在a、b、c之后已经是4字节对齐的地址了,因此d可以直接存放在c之后,对齐

到4个字节。

按照上面的分析,我们可以画出EXAMPLE4在内存中的分布示意图:abccdddd

可以看到EXAMPLE4虽然比EXAMPLE3多了一个变量,但与EXAMPLE3一样同样

占有8个字节。我们再使用sizeof和OFFSET计算EXAMPLE3的数据进行验证,如下:sizeof(EXAMPLE4)8OFFSET(EXAMPLE4,a)0OFFSET(EXAMPLE4,b)1OFFSET(EXAMPLE4,c)2OFFSET(EXAMPLE4,d)4例5:typedefstructexample5{shorta;charb;}EXAMPLE5;

再来看EXAMPLE5,按照上面介绍的规则你是否会认为它的长度是3?

EXAMPLE5在内存中分布示意图如下:

aab

结构体不但要保证其存放的地址需要对齐到其内部变量类型中最长对齐长度的长度的

整数倍,其长度也要保证是其内部变量类型中最长的对齐长度的整数倍。EXAMPLE5中最

长的对齐长度变量是a,2个字节,因此它也必须是2字节的整数倍,所以在b之后需要填

充1个字节。因此sizeof(EXAMPLE5)为4。

例6:typedefstructexample6