我是之前在試驗STM32G031G8U6單片機內(nèi)部FLASH讀操作時候發(fā)現(xiàn)的這個問題:STM32F103的flash數(shù)據(jù)可以從任意地址讀,而STM32G031G8U6就不行,讀數(shù)據(jù)地址不對齊的話會死機,
關于什么是非對齊訪問,可以參考下圖1,來源于CM3權威指南CH5相關內(nèi)容:
圖1
先說結(jié)論:
1.Cortex-M0內(nèi)核不支持****非對齊訪問
2.Cortex-M3內(nèi)核支持非對齊訪問
3.intel i5支持非對齊訪問
4 .是否支持對存儲器的非對齊訪問取決于具體使用的內(nèi)核
本次試驗的完成耽誤了很久,因為最近一周我得新冠了,體質(zhì)差得新冠了啥也做不了。以下記錄了我的 實驗驗證過程,過程很長, 沒時間看的小伙伴了解上面的結(jié)論就夠了 。我將在三種不同的內(nèi)核平臺上進行測試,為簡化試驗,我只在全局區(qū)驗證,原因在于:相同數(shù)據(jù)類型變量在全局區(qū)和棧區(qū)的內(nèi)存地址排列方式是不同的
一、在搭載intel i5 64位內(nèi)核、安裝了64位windows的電腦上,創(chuàng)建一個win32控制臺應用程序,編譯方式選擇release模式**(關于CPU位數(shù)、操作系統(tǒng)位數(shù)、應用程序位數(shù)三者的內(nèi)部關系這里不展開,我暫時也不咋清楚,本篇只關注非對齊訪問這個主題)**
1 單字節(jié)數(shù)據(jù)類型: 全局變量緊挨著,按地址遞減排列, 可以在任意地址訪問,當然了單字節(jié)壓根不存在非對齊訪問的問題。 以下為試驗代碼,圖2為執(zhí)行結(jié)果
#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;
}
執(zhí)行結(jié)果如下:
圖2
2 兩字節(jié)數(shù)據(jù)類型:全局變量的首地址自動按4字節(jié)對齊,因此沒有挨著排列(2個padding字節(jié)) ,并且地址是從高到低的 ,但是可以進行非對齊訪問。 以下試驗代碼的執(zhí)行結(jié)果如圖3,對變量c地址的兩字節(jié)賦值操作,賦值后,它自個兒在內(nèi)存中跨了兩個4字節(jié)區(qū)域,也就是數(shù)據(jù)本身沒有對齊,這個和它向下生長有關系,和我這里討論的非對齊訪問不是一回事,然后讀的時候能順利讀出來,程序沒有發(fā)生死機,也就是可以非對齊訪問。
#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;
}
執(zhí)行結(jié)果如下:
圖3
3.四字節(jié)數(shù)據(jù)類型: 全局變量的首地址自動按4字節(jié)對齊,緊挨著、地址遞減排列,可以進行非對齊訪問。 執(zhí)行結(jié)果如圖4,在這個平臺環(huán)境下,四字節(jié)數(shù)據(jù)的放置規(guī)則:首地址按四字節(jié)對齊,但是數(shù)據(jù)本身不對齊,和兩字節(jié)數(shù)據(jù)一樣,拆成了上下兩部分,另外三個字節(jié)放到高地址的四個字節(jié)區(qū)域。
#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;
}
執(zhí)行結(jié)果如下:
圖4
二、32位的cm0內(nèi)核,stm32g031單片機,裸機編程
1 單字節(jié)數(shù)據(jù)類型:全局變量緊挨著,和windows平臺不一樣, cm0內(nèi)核平臺在這里按地址遞增排列 ,可以在任意地址訪問,當然了單字節(jié)壓根不存在非對齊訪問的問題。以下為試驗代碼,圖5為執(zhí)行結(jié)果
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.兩字節(jié)數(shù)據(jù)類型:全局變量的首地址自動按2字節(jié)對齊,因此沒有挨著排列(2個padding字節(jié)) ,并且地址是從低到高的 ,不可以進行非對齊訪問(無論進行非對齊的讀還是寫都會導致死機)。 以下試驗代碼的執(zhí)行結(jié)果如圖6,可通過解開屏蔽的代碼來驗證首地址對齊規(guī)則和非對齊訪問規(guī)則
//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.四字節(jié)數(shù)據(jù)類型: 全局變量的首地址自動按4字節(jié)對齊,緊挨著、地址遞增排列,不可以進行非對齊訪問 (無論進行非對齊的讀還是寫都會導致死機) 。 驗證代碼的執(zhí)行結(jié)果如圖7,可以通過解開屏蔽的代碼來驗證首地址對齊規(guī)則和非對齊訪問規(guī)則
//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內(nèi)核,stm32f103單片機,裸機編程
1.單字節(jié)數(shù)據(jù)類型:全局變量緊挨著,和windows平臺不一樣, cm3內(nèi)核平臺在這里按地址遞增排列 ,可以在任意地址訪問,當然了單字節(jié)壓根不存在非對齊訪問的問題。另外 和cm0內(nèi)核不一樣的是,cm3的編譯嚴格要求變量聲明在局部作用域的最前面 ,比如以下驗證代碼我將指針p的聲明放到了最前面,否則編譯將無法通過,圖8為執(zhí)行結(jié)果
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.兩字節(jié)數(shù)據(jù)類型:全局變量的首地址自動按2字節(jié)對齊,因此沒有挨著排列(2個padding字節(jié)) ,并且地址是從低到高的 ,可以進行非對齊訪問。 以下試驗代碼的執(zhí)行結(jié)果如圖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.四字節(jié)數(shù)據(jù)類型: 全局變量的首地址自動按4字節(jié)對齊,緊挨著、地址遞增排列,可以進行非對齊訪問。 如下驗證代碼的執(zhí)行結(jié)果如圖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
評論
查看更多