我是之前在试验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.h"
uint8_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.h"
uint16_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.h"
uint32_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("