ITPub博客

首页 > > > 小心内存对齐

小心内存对齐

作者:dapan 时间:2019-09-27 10:02:51 0 删除 编辑

什么是内存对齐?

CPU在读取内存地址的时候,一定按照一定的偏移量去读取,不知道你发现了没有,我们没有看到一个变量的大小是 3 个字节的,都是 1 个字节,2个字节,4个字节,8个字节,16个字节,32个字节。

为什么会这样呢?因为CPU设计的时候,没有一个 3 、5、7、9这样的模子,因为设计这样的模子非常费劲。

为什么要内存对齐?

之前网上有一个一个例子,如果一个变量int 的起始地址偏移是3,那么CPU要取这个地址上的数据,需要取两次,为什么呢?


假设一个变量 在内存的位置 从地址 1开始存放数据,因为这个是int类型,它占用4个字节的内存空间。

我们用一个int 的模子「int模子是4个字节」来卡这个数据,实际上是这样操作的,第一次卡模子,只能从0开始

第二次卡模子,再从3位置开始

从图片上可以明显看出来,我们需要CPU卡两次模子,才取到在内存里面的 int 变量

如果int 是按照内存对齐的方式存放的呢?


很明显,我们只需要卡一次模子就可以取到数据了。

内存对齐引发的问题

前两天,我们群上有一个同学,发了一份这样的代码出来,代码写得还是有一点风骚的,这位同学说,这个代码在A单片机上运行得非常OK,也已经出货了几万台了,但是这个代码移植到单片机B上后,就挂了,大家帮忙看看这个代码有什么问题?


uint8_t buf[16];
uint8_t i;
uint16_t data;
for (i=0; i<16; i+=2)
{ 
    data = *((uint16_t *)&buf[i]);
}

问题的原因

经过一段激烈的讨论,后面发现是内存对齐的问题,一个群友画了一个图,写的很清楚了,因为char 是对单字节对齐的,所以没有问题,但是uint16_t 默认是对双字对齐的,所以如果初始地址是奇数的话,最后就出现内存越界了。


一个图片说明不同数据类型的内存对齐



使用#pragma pack()限定内存对齐

我们可以使用宏 #pragma pack() 来指定内存对齐的方式,这里我就不展开说明了,如果说明太多了,后面用一个文章来单独说明,喜欢了解的同学可以百度看看,内容还是比较多的。

内存对齐可以提升CPU效率

我们知道单字节对齐的效率应该是最低的,我给两个代码给大家看看,比较一下,单字节和双字节内存对齐对CPU效率的影响。

单字节对齐函数


void Munge8( void ∗data, uint32_t size )

{

uint8_t ∗data8 = (uint8_t∗) data; uint8_t ∗data8End = data8 + size;
while( data8 != data8End ) { ∗data8++ = ‑∗data8; }

}

双字节对齐函数


void Munge16( void ∗data, uint32_t size ) { uint16_t ∗data16 = (uint16_t∗) data; uint16_t ∗data16End = data16 + (size >> 1); /∗ Divide size by 2. ∗/ uint8_t ∗data8 = (uint8_t∗) data16End; uint8_t ∗data8End = data8 + (size & 0x00000001); /∗ Strip upper 31 bits. ∗/
while( data16 != data16End ) { ∗data16++ = ‑∗data16; } while( data8 != data8End ) { ∗data8++ = ‑∗data8; }}

这个代码大家可以直接拿去执行试试,最终的结果如下面的图片



果修改成 4 字节对齐的话,结果将会更加有意思

四字对齐函数

void Munge32( void ∗data, uint32_t size )

{

uint32_t ∗data32 = (uint32_t∗) data; uint32_t ∗data32End = data32 + (size >> 2); /∗ Divide size by 4. ∗/ uint8_t ∗data8 = (uint8_t∗) data32End; uint8_t ∗data8End = data8 + (size & 0x00000003); /∗ Strip upper 30 bits. ∗/
while( data32 != data32End ) { ∗data32++ = ‑∗data32; } while( data8 != data8End ) { ∗data8++ = ‑∗data8; }

}


他们的效率主要体现在字节不对齐的时候,如果字节默认已经是对齐的时候,那效果更是快得飞起。

参考

developer.ibm.com/artic


留个问题

上面有问题的代码如何修改?

回答正确的前三名,我发心意红包






扫码或长按关注

回复「 加群 」进入技术群聊

原文作者:;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2020-08-07

  • 博文量
    4504
  • 访问量
    87401