内存对齐问题是每一个c程序员都应该考虑过的问题,c编译器的规则明确规定了对齐问题,就是一个struct中按照最长的类型对齐,比如考虑下面的结构体:
struct abc
{
char a;
int b;
char c;
}
在32位x86机器上它的大小是多少呢?是12,为什么呢?因为该结构体中最长的类型是int,因此需要按照4字节对齐,因此a和c后面都要pad进去3个无辜的字节,是不是太浪费了呢?是的,但是如果该结构体不是非这样设计不可(如果是网络协议之类的既定协议就不能随意更换,这涉及协议设计问题,不深谈),你完全可以将b和c对换一下位置,这样的话,大小将会成为8,能否再节省一些呢?可以,那就是用pack伪指令取消编译器的内存对齐,但是代价是效率更低以及更不安全,首先看一下为何效率更低。
32位cpu上,数据总线是32位的,一次存取32位的数据,如果不对齐,那么考虑cpu访问完a然后访问b的情况,访问完a,一次存取了a开始的32位4字节的数据,此时仅有开始的8个字节是有效的(以下不考虑大小端),后面的3个字节被丢弃,然后访问b,继续向前推进地址总线,就是地址为a的地址加4,那么得到的是4字节b的最后一个字节和c然后跟着一个无关的两个字节的结构外的数据,为了得到b的前三个字节,地址必须退回到a的地址,然后抛弃a从而得到后面的三个字节,或者直接退回到a的地址的下一个地址,但是这种情况下直接就可以得到b,前面得到的b的最后一个字节就没有用了,不管怎样最少需要三次访存,即使cpu可以精确到正好将地址回退到b的地址,回退总线也没有顺序推进总线高效。如果是4字节对齐的,那么在访问完a后,直接访问下一个4字节就可以了,这就体现了高效性。
很多人都认为写比读更麻烦,对于cpu而言是这样吗?cpu要想读一个数据,第一步必须将地址总线设置好,然后在下一个周期才能在数据总线得到数据,然而对于写操作,cpu可以一步完成,直接将地址放到地址总线,并且将数据置于数据总线就可以了,一个周期就可以完成,因此对于cpu而言,写比读更容易。以上是一个无关紧要的话题,现在考虑多cpu的情况,多个cpu连接的是同一个地址总线以及同一个数据总线,也就是说内存是单进单出的,至于怎么路由到多个cpu并不是存储器本身的问题,一般而言一个总线周期只能由一个cpu控制总线,如果数据没有对齐,那么由于需要额外的很多次总线操作,那么就会出现一种情况,即一个cpu操作完了总线读到了4个字节中的1个字节,还剩下3个字节没有读,此时其它cpu控制了总线,在剩下的3个字节的地址处写入了数据,那么等到第一个cpu再次控制总线时就会得到错误的数据,虽然这件事事实上不会发生,但是必然拥有制止这件事发生的措施,既然有了措施就一定有了开销,如果数据是对齐的,那么这种开销就不会有,简单的说也是提高了效率。
因此很多32位的cpu直接禁掉了地址总线的低两位,所以该类cpu只能访问4字节对齐的数据,另一些cpu会捕获访问不对齐的数据,这里有一个很古老的话题,就是为何一个字节是8位,因为老的成型的cpu的总线宽度是8位!实际上当今的cpu中这类对齐优化已经没有纯粹的优化意义了,因为局部原理连同cpu的cache会掩盖对齐得到的优化。cpu的优化原则是对于长度为n的数据的地址被分配到n的整数倍,这样就会在访问完该数据后顺序的到达下一个数据而不需要切割操作。