存储器系统的非对齐访问

我是之前在试验stm32g031g8u6单片机内部flash读操作时候发现的这个问题:stm32f103的flash数据可以从任意地址读,而stm32g031g8u6就不行,读数据地址不对齐的话会死机,
关于什么是非对齐访问,可以参考下图1,来源于cm3权威指南ch5相关内容:
图1
先说结论:
1.cortex-m0内核不支持****非对齐访问
2.cortex-m3内核支持非对齐访问
3.intel i5支持非对齐访问
4 .是否支持对存储器的非对齐访问取决于具体使用的内核
本次试验的完成耽误了很久,因为最近一周我得新冠了,体质差得新冠了啥也做不了。以下记录了我的 实验验证过程,过程很长, 没时间看的小伙伴了解上面的结论就够了 。我将在三种不同的内核平台上进行测试,为简化试验,我只在全局区验证,原因在于:相同数据类型变量在全局区和栈区的内存地址排列方式是不同的
一、在搭载intel i5 64位内核、安装了64位windows的电脑上,创建一个win32控制台应用程序,编译方式选择release模式**(关于cpu位数、操作系统位数、应用程序位数三者的内部关系这里不展开,我暂时也不咋清楚,本篇只关注非对齐访问这个主题)**
1 单字节数据类型: 全局变量紧挨着,按地址递减排列, 可以在任意地址访问,当然了单字节压根不存在非对齐访问的问题。 以下为试验代码,图2为执行结果
#include stdafx.h#include stdint.huint8_t a, b, c, d, e, f;int _tmain(int argc, _tchar* argv[]){ int i = 0; *(uint8_t*)&a = 0x01; *(uint8_t*)&b = 0x02; *(uint8_t*)&c = 0x03; *(uint8_t*)&d = 0x04; *(uint8_t*)&e = 0x05; *(uint8_t*)&f = 0x06; printf(global var addr: %p %p %p %p %p %p, &a, &b, &c, &d, &e, &f); printf(global var value: %x %x %x %x %x %x, a, b, c, d, e, f); printf(the memory region:); uint8_t* p = (uint8_t*)(((uint32_t)&a) % 4 + (uint32_t)&a); printf(global var addr: %p, p); for (i = 0; i < 20; i++) { if (i % 4 == 0) { printf(); } printf(0x%02x , *(uint8_t*)(p - i)); } printf(); getchar(); return 0;}执行结果如下:
图2
2 两字节数据类型:全局变量的首地址自动按4字节对齐,因此没有挨着排列(2个padding字节) ,并且地址是从高到低的 ,但是可以进行非对齐访问。 以下试验代码的执行结果如图3,对变量c地址的两字节赋值操作,赋值后,它自个儿在内存中跨了两个4字节区域,也就是数据本身没有对齐,这个和它向下生长有关系,和我这里讨论的非对齐访问不是一回事,然后读的时候能顺利读出来,程序没有发生死机,也就是可以非对齐访问。
#include stdafx.h#include stdint.huint16_t a, b, c, d;int _tmain(int argc, _tchar* argv[]){ int i = 0; *(uint16_t*)((uint8_t*)&a + 2) = 0x01ff; *(uint16_t*)((uint8_t*)&b + 1) = 0x02ff; *(uint16_t*)&c = 0x03ff; *(uint16_t*)&d = 0x04ff; printf(global var addr: %p %p %p %p, &a, &b, &c, &d); printf(global var value: %x %x %x %x %x %x, *(uint16_t*)((uint8_t*)&a + 2), *(uint16_t*)((uint8_t*)&b + 1), a, b, c, d); printf(the memory region:); for (i = 0; i < 20; i++) { if (i % 4 == 0) { printf(); } printf(0x%02x , *(uint8_t*)((uint8_t*)&a - i + 4)); } printf(); getchar(); return 0;}执行结果如下:
图3
3.四字节数据类型: 全局变量的首地址自动按4字节对齐,紧挨着、地址递减排列,可以进行非对齐访问。 执行结果如图4,在这个平台环境下,四字节数据的放置规则:首地址按四字节对齐,但是数据本身不对齐,和两字节数据一样,拆成了上下两部分,另外三个字节放到高地址的四个字节区域。
#include stdafx.h#include stdint.huint32_t a, b;int _tmain(int argc, _tchar* argv[]){ int i = 0; *(uint32_t*)((uint8_t*)&a+1) = 0x01020304; *(uint32_t*)&b = 0x05060708; printf(global var addr: %p %p, &a, &b); printf(global var value: %08x %08x, a, b); printf(the memory region:); uint8_t* p = (uint8_t*)(((uint32_t)&a) % 4 + (uint32_t)&a); printf(global var addr: %p, p); for (i = 0; i < 20; i++) { if (i % 4 == 0) { printf(); } printf(0x%02x , *(uint8_t*)(p - i + 4)); } printf(); getchar(); return 0;}执行结果如下:
图4
二、32位的cm0内核,stm32g031单片机,裸机编程
1 单字节数据类型:全局变量紧挨着,和windows平台不一样, cm0内核平台在这里按地址递增排列 ,可以在任意地址访问,当然了单字节压根不存在非对齐访问的问题。以下为试验代码,图5为执行结果
uint8_t a, b, c, d, e, f;int main(void){ hal_init(); systemclock_config(); mx_dma_init(); mx_usart2_uart_init(230400); int i = 0; *(uint8_t*)&a = 0x01; *(uint8_t*)&b = 0x02; *(uint8_t*)&c = 0x03; *(uint8_t*)&d = 0x04; *(uint8_t*)&e = 0x05; *(uint8_t*)&f = 0x06; printf(global var addr: %p %p %p %p %p %p, &a, &b, &c, &d, &e, &f); printf(global var value: %x %x %x %x %x %x, a, b, c, d, e, f); printf(the memory region:); uint8_t* p = (uint8_t*)((uint32_t)&a - ((uint32_t)&a) % 4); printf(global var addr: %p, p); for (i = 0; i < 20; i++) { if (i % 4 == 0) { printf(); } printf(0x%02x , *(uint8_t*)(p + i)); } printf(); while (1) { }}
图5
2.两字节数据类型:全局变量的首地址自动按2字节对齐,因此没有挨着排列(2个padding字节) ,并且地址是从低到高的 ,不可以进行非对齐访问(无论进行非对齐的读还是写都会导致死机)。 以下试验代码的执行结果如图6,可通过解开屏蔽的代码来验证首地址对齐规则和非对齐访问规则
//uint8_t padding;uint16_t a, b, c, d;int main(void){ hal_init(); systemclock_config(); mx_dma_init(); mx_usart2_uart_init(230400); int i = 0; *(uint16_t*)((uint8_t*)&a + 2) = 0x01ff; //*(uint16_t*)((uint8_t*)&b + 1) = 0x02ff; *(uint16_t*)&c = 0x03ff; *(uint16_t*)&d = 0x04ff; printf(global var addr: %p %p %p %p, &a, &b, &c, &d); //printf(global var value: %x %x %x %x %x %x, *(uint16_t*)((uint8_t*)&a + 2), *(uint16_t*)((uint8_t*)&b + 1), a, b, c, d); printf(global var value: %x %x %x %x %x %x, *(uint16_t*)((uint8_t*)&a + 2), *(uint16_t*)((uint8_t*)&b + 0), a, b, c, d); printf(the memory region:); for (i = 0; i < 20; i++) { if (i % 4 == 0) { printf(); } printf(0x%02x , *(uint8_t*)((uint8_t*)&a + i)); } printf(); while (1) { }}
图6
3.四字节数据类型: 全局变量的首地址自动按4字节对齐,紧挨着、地址递增排列,不可以进行非对齐访问 (无论进行非对齐的读还是写都会导致死机) 。 验证代码的执行结果如图7,可以通过解开屏蔽的代码来验证首地址对齐规则和非对齐访问规则
//uint8_t padding;uint32_t a, b;int main(void){ hal_init(); systemclock_config(); mx_dma_init(); mx_usart2_uart_init(230400); int i = 0; //*(uint32_t*)((uint8_t*)&a+1) = 0x01020304; *(uint32_t*)((uint8_t*)&a) = 0x01020304; *(uint32_t*)&b = 0x05060708; printf(global var addr: %p %p, &a, &b); printf(global var value: %08x %08x, a, b); printf(the memory region:); uint8_t* p = (uint8_t*)((uint32_t)&a - ((uint32_t)&a) % 4); printf(global var addr: %p, p); for (i = 0; i < 20; i++) { if (i % 4 == 0) { printf(); } printf(0x%02x , *(uint8_t*)(p + i)); } printf();
图7
三、32位的cm3内核,stm32f103单片机,裸机编程
1.单字节数据类型:全局变量紧挨着,和windows平台不一样, cm3内核平台在这里按地址递增排列 ,可以在任意地址访问,当然了单字节压根不存在非对齐访问的问题。另外 和cm0内核不一样的是,cm3的编译严格要求变量声明在局部作用域的最前面 ,比如以下验证代码我将指针p的声明放到了最前面,否则编译将无法通过,图8为执行结果
uint8_t a, b, c, d, e, f; int main(void){ int i = 0; uint8_t* p = null; uart_init(115200); *(uint8_t*)&a = 0x01; *(uint8_t*)&b = 0x02; *(uint8_t*)&c = 0x03; *(uint8_t*)&d = 0x04; *(uint8_t*)&e = 0x05; *(uint8_t*)&f = 0x06; printf(global var addr: %p %p %p %p %p %p, &a, &b, &c, &d, &e, &f); printf(global var value: %x %x %x %x %x %x, a, b, c, d, e, f); printf(the memory region:); p = (uint8_t*)((uint32_t)&a - ((uint32_t)&a) % 4); printf(global var addr: %p, p); for (i = 0; i < 20; i++) { if (i % 4 == 0) { printf(); } printf(0x%02x , *(uint8_t*)(p + i)); } printf(); while (1) { } }
图8
2.两字节数据类型:全局变量的首地址自动按2字节对齐,因此没有挨着排列(2个padding字节) ,并且地址是从低到高的 ,可以进行非对齐访问。 以下试验代码的执行结果如图9
uint8_t padding;uint16_t a, b, c, d; int main(void){ int i = 0; uart_init(115200); *(uint16_t*)((uint8_t*)&a + 2) = 0x01ff; *(uint16_t*)((uint8_t*)&b + 1) = 0x02ff; //*(uint16_t*)&c = 0x03ff; *(uint16_t*)&d = 0x04ff; printf(global var addr: %p %p %p %p, &a, &b, &c, &d); printf(global var value: %x %x %x %x %x %x, *(uint16_t*)((uint8_t*)&a + 2), *(uint16_t*)((uint8_t*)&b + 1), a, b, c, d); //printf(global var value: %x %x %x %x %x %x, *(uint16_t*)((uint8_t*)&a + 2), *(uint16_t*)((uint8_t*)&b + 0), a, b, c, d); printf(the memory region:); for (i = 0; i < 20; i++) { if (i % 4 == 0) { printf(); } printf(0x%02x , *(uint8_t*)((uint8_t*)&a + i)); } printf(); while (1) { } }
图9
3.四字节数据类型: 全局变量的首地址自动按4字节对齐,紧挨着、地址递增排列,可以进行非对齐访问。 如下验证代码的执行结果如图10
uint8_t padding;uint32_t a, b; int main(void){ int i = 0; uint8_t* p = null; uart_init(115200); *(uint32_t*)((uint8_t*)&a+1) = 0x01020304; //*(uint32_t*)((uint8_t*)&a) = 0x01020304; *(uint32_t*)&b = 0x05060708; printf(global var addr: %p %p, &a, &b); printf(global var value: %08x %08x, a, b); printf(the memory region:); p = (uint8_t*)((uint32_t)&a - ((uint32_t)&a) % 4); printf(global var addr: %p, p); for (i = 0; i < 20; i++) { if (i % 4 == 0) { printf(); } printf(0x%02x , *(uint8_t*)(p + i)); } printf(); while (1) { } }
图10

iQOO手机采用44W超快闪充只需45分钟即可充满电在续航方面也堪称惊人
挺进3G芯片 展讯追赶联发科
单片机上常用GB2312、GBK汉字取模与字库
双积分政策的实施有效的推动了新能源汽车的转型升级
7类网线与超6类网线的区别
存储器系统的非对齐访问
5G建设加快意味着什么
地平线GitLab使用指导
盘点2015年1至7月半导体产业合并案
东部华侨城网络建设项目提供了良好的网络建设实践经验
新的RDM系列硅光电倍增管 (SiPM) 阵列
中兴Axon 40系列新品即将首发,配备独立安全芯片
优傲机器人系统改造,带来更大应用价值
stm32入门开发板选野火还是正点原子呢?
电流检测电阻器基本概念:如何使用万用表测量电流
国芯科技:自研车用MCU新品内测成功,适用于动力总成等领域
试验变压器与电力变压器的区别
基于MAX4992 0-70V电流检测放大器解决方案设计
外媒:小米计划在印度扩张其商店网络,攻占市场第一
你涨我也得涨!电池的价格飙升,可能会影响iPhone 8售价 不愧是肾8啊!