存储器系统的非对齐访问

发布时间:2023-10-30  

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

文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>