什么是内存对齐?
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/
留个问题
上面有问题的代码如何修改?
回答正确的前三名,我发心意红包
扫码或长按关注
回复「 加群 」进入技术群聊