[ARM-2D 专题]5 MDK编译器一个旧版本-Ofast优化bug的问题及解决办法

最近开始大量基于ARM-2D开发应用项目,为了达到最佳性能,我们使用了编译器的许多特殊技能,其中就包含了-Ofast优化,很不幸,一不小心踩坑了。

案发情况如下:

使用的MDK版本5.36,编译器6.16

优化选项配置如下:

不使用mcirolib

代码比较简单,读写一个Norflash数据:

bash 复制代码
uint8_t sFLASH_SendByte(uint8_t byte)
{
	uint32_t i;
	i = 0;
  while ((SPI_I2S_GetFlagStatus(sFLASH_SPI, SPI_I2S_FLAG_TXE) == RESET) && (i++ < 5000)); //set timeout
  SPI_SendData(sFLASH_SPI, byte);

	i = 0;
  while ((SPI_I2S_GetFlagStatus(sFLASH_SPI, SPI_I2S_FLAG_RXNE) == RESET) && (i++ < 5000)); //set timeout
  return (uint8_t)SPI_GetData(sFLASH_SPI);
  }

以前使用microlib库编译,运行良好,所以也没有在意问题,现在使用arm-2d,不使用microlib编译,运行,读数据错误了。一开始莫名奇妙的,采用各种组合验证,得出以下结论:

测试情况:

  1. 优化开-Ofast,使用microlib编译,代码中的i不用volatile修饰,功能正常
  2. 优化开-Ofast,不使用microlib编译,代码中的i不用volatile修饰,功能异常
  3. 优化开-O1,不使用microlib编译,代码中的i不用volatile修饰,功能正常
  4. 优化开-Ofast,不使用microlib编译,代码中的i用volatile修饰,功能正常

初步结论,看起来就是编译器-Ofast,并且不使用microlib库的时候,优化出了问题。代码逻辑上看起来没有任何问题(表面上如此,具体解释在这种情况不同的编译器有不同的说法,编译器说了算)。

追踪了一下反汇编代码:

确实发现了一个优化的问题,如图中所示,R6寄存器没做任何初始化,后面就和R0比较(对应代码i<5000),导致延时时间不够,寄存器数据读取错误。

后来和arm2d的作者讨论了一下,确定了结论:编译器的未定义行为或者说bug

参考:编译器的无副作用代码

为了验证这个问题,安装最新的MDK版本:

还是这一段代码,其他配置一样,编译,链接,下载,测试。运行正常,说明最新的编译器已经修正了这个不是bug的bug。我们看看它生成的代码:

这下子没有问题了,明显看到了对两个变量R6,R4的初始化,运行结果也正确了。

那么对于上面那一段代码,我们要如何来写,才能防止编译器的优化或者不确定行为呢?进而确保我们写出的代码具有健壮性和与编译器的无关性,下面两种改写都有效,共参考:

bash 复制代码
uint8_t sFLASH_SendByte(uint8_t byte)
{
	uint32_t i; 
	i = 0;
  while ((SPI_I2S_GetFlagStatus(sFLASH_SPI, SPI_I2S_FLAG_TXE) == RESET) && (i++ < 5000)) __NOP(); //set timeout
  SPI_SendData(sFLASH_SPI, byte);
	i = 0;
  while ((SPI_I2S_GetFlagStatus(sFLASH_SPI, SPI_I2S_FLAG_RXNE) == RESET) && (i++ < 5000)) _NOP(); //set timeout
  return (uint8_t)SPI_GetData(sFLASH_SPI);
  }

这一种写法,在while后面增加一条_NOP()指令,阻止编译器对看似无用的i++的优化,保证不被未定义行为的优化。

bash 复制代码
	uint8_t sFLASH_SendByte(uint8_t byte)
{
	for(i = 0;i < 1000;i++)
	{
		if ((sFLASH_SPI->SR & SPI_I2S_FLAG_TXE) != (uint16_t)RESET)
			break;
	}
	SPI_SendData(sFLASH_SPI, byte);
	for(i = 0;i < 1000;i++)
	{
		if ((sFLASH_SPI->SR & SPI_I2S_FLAG_RXNE) != (uint16_t)RESET)
			break;
	}
	return (uint8_t)SPI_GetData(sFLASH_SPI);

这一种写法,用循环方式,明确的告诉编译器,i是有用的,也可以达到这个目的。

还有一种方案也可以解决这个问题,就是定义i变量的时候用volatile修正,但是一般不建议采用这种方式。

bash 复制代码
volatile uint32_t i; 

编译器的坑,很难对付,我们理解一下编译器的行为,站在编译器的角度去思考问题,平时写出更优质的代码,才能完全防止这种情况的发生。

文章原创,欢迎转载,请注明出处,未经书面允许,不得用于商业用途

相关推荐
多看多敲多思考12 小时前
华润微CS32ME10 MCU使用教程(2)---CS32ME10之UART串口模块使用
stm32·单片机·嵌入式硬件·mcu
山后太阳14 小时前
Keil5(MDK-ARM)完整下载安装教程+入门教程:从零搭建STM32开发环境
arm开发·stm32·嵌入式硬件
zz_lzh14 小时前
arm版AI牛马:armbian(rk3588)设备部署openclaw
arm开发·人工智能·arm
lzj_pxxw15 小时前
W25Q64存储芯片 软件设计刚需常识
stm32·单片机·嵌入式硬件·mcu·学习
电子科技圈2 天前
芯科科技在蓝牙亚洲大会展示汽车与边缘AI前沿蓝牙创新技术, 解锁车用、家居、健康及工商业等应用场景
人工智能·科技·嵌入式硬件·mcu·物联网·网络安全·汽车
lanxiao88882 天前
F1C100S 内核
arm开发
杰杰桀桀桀2 天前
基于stm32ARM库函数的IIR二阶巴特沃斯低通滤波器--附完整代码
arm开发·stm32·嵌入式硬件·数字滤波器·巴特沃斯低通滤波
TBrL7UtdTELTTdut4BAL2 天前
ARM Cortex-A53 (无AES)平台加密网络转发性能测试与对比分析
arm开发·集成测试
来生硬件工程师2 天前
【程序库】 MutiButton 按键库
c语言·笔记·stm32·单片机·mcu·嵌入式实时数据库
多看多敲多思考2 天前
华润微CS32ME10 MCU使用教程(1)---CS32ME10之GPIO使用
c语言·stm32·单片机·嵌入式硬件·mcu