小言_互联网的博客

小心内存对齐

348人阅读  评论(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;	
    }	
}

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

参考

https://developer.ibm.com/articles/pa-dalign/

留个问题

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

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

扫码或长按关注

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


转载:https://blog.csdn.net/weiqifa0/article/details/101487521
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场