存储器系统的非对齐访问

我是之前在试验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(“

“);

while (1)

{

}

}

图10

发表评论