变量定义的顺序不同,MDK的优化结果却不一样

2019-07-14 14:49发布


问题描述:
使用MDK4或5编译ST的USB-host库时,里面有个USB_OTG_BSP_uDelay函数,本函数本身在-03级优化下是没有问题的,但我手痒,对里面的变量的定义顺序做了一下修改,结果-o3优化下出问题了(表现为该延时函数不准确,导致部分U盘枚举失败),请教大师分析一下:
错误改法: 将定义 “const Uint32 utime = (Uint32)120 * usec / 7;” 放到 “__IO Uint32 uCount = 0;” 代码前面。
我的问题:仅仅是变量定义的顺序不同,为什么优化后结果却不一样(不是存储对齐的问题)?


正常的代码如下:
void USB_OTG_BSP_uDelay(const uint32_t usec)
{
__IO Uint32 uCount = 0;
const Uint32 uTime = (Uint32)120 * usec / 7;

while (1)
{
   if (++uCount > uTime)
   {
    return;
   }
}
}

错误的代码如下:
void USB_OTG_BSP_uDelay(const uint32_t usec)
{
const Uint32 uTime = (Uint32)120 * usec / 7;
__IO Uint32 uCount = 0;


while (1)
{
   if (++uCount > uTime)
   {
    return;
   }
}
}


附件是正常的与错误的反汇编截图,请大家指点!





友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
该问题目前已经被作者或者管理员关闭, 无法添加新回复
15条回答
jfuwre
1楼-- · 2019-07-15 22:55
aril1227 发表于 2019-1-4 12:09
哦,是延时函数不达标引起的。我不熟悉汇编,还要麻烦您看下顶帖的反汇编对比图。

如果是软延时呢,那就是很正常不过的了,因为有时候编译器会采用不同的优化策略,即使是非常小的改动,甚至无关的改动!!!硬延时的话,那就要看看数值是否正确了!!!如果是软延时,个人建议可以加大一下数值,或者直接用宏nop,如果要求非常高的化!!!!
aril1227
2楼-- · 2019-07-16 04:30
jfuwre 发表于 2019-1-4 12:19
如果是软延时呢,那就是很正常不过的了,因为有时候编译器会采用不同的优化策略,即使是非常小的改动,甚至无关的改动!!!硬延时的话,那就要看看数值是否正确了!!!如果是软延时,个人建议可以加大一下数值,或者直接用宏nop,如果要求非常高的化!!!! ...

本帖最后由 野马-425178 于 2017-5-12 12:51 编辑

做了些实验,发现情况是这样的:
USB_OTG_BSP_uDelay函数在优化后如果有调用LDR汇编指令的时候即满足设计微秒级延时要求,但如果优化后没有调用LDR指令则延时时间被缩短且不符合要求了;
ST库提供的原始代码在优化时刚好有调用LDR指令(因为该变量定义的顺序刚好使MDK优化时认为无法保存寄存器变量,需要每次LDR重载),所以就没有问题;
而我把变量定义顺序修改后,经过MDK优化则把LDR指令去掉了,但是MDK也没有错,因为uTime是const变量,如果优化策略认为不需要每次重载的时候就不需要LDR了,结果导致时间不准确。
实际上,最安全的做法应该是要使用“__IO”来修饰变量“uTime”,而不是用“const”。

版主的回复也是对的,因为这个函数设计的时候就是用const修饰了uTime变量(不一定每次都重新加载),所以LDR被优化掉也算是正常,而我正好又是需要这个指令的运行时间。

在阿莫上刚好有个帖子,关于MDK优化省略了LDR操作的问题,里面描述了和我这个问题很类似。
https://www.amobbs.com/thread-4214047-1-1.html

该网址下以下内容和我的问题类似:
1、你好,我发现如果那个while(unIdleCount!=200);   不是一个空等待的循环,(当然,里面不是的环就不是等待2Ms了,而是在这2MS里一直执行某个操作了),这样的话就不会出现这种情况了,在比较之前
   会有一个LDR的操作,保证读取的是RAM里的数据,而这时我并没有用volatile修饰他,而且我的优化等级选的是最高的3也没关系

2、另一个,我发现,只要unIdleCount=0不是紧挨着下面那个判断语句,两句之间隔了一些函数,就算这些函数没有操作unIdleCount,在比较之前也会有那个LDR命令获得unIdleCount最新的值

   22:         TIM3_Int_Init(500,7199);//10Khz的计数频率,计数到5000为500ms   
     23:           
0x08000A08 8820      LDRH     r0,[r4,#0x00]   //这条语句是在下面那个判断之前突然出现的,用来获得最新的值  
     24:         while(unIdleCount!=10);
     25:                  
0x08000A0A 280A      CMP      r0,#0x0A
0x08000A0C D1FD      BNE      0x08000A0A

同样,这时我也没有用volatile修饰unIdleCount,那应该就是这时寄存器里面没有保存unIdleCount了,(因为这之前的操作保存不了了),所以会重新读,

3、后来我又脑残的做了几个实验,发现在unIdleCount=0;和while(unIdleCount!=10);之间相隔的有东西的时候,不管是函数(需要跳转)还是简单运算,只要编译器有办法用一个寄存器保存unIdleCount 的值
   同时又能完成这之间的运算,他就会直接比较寄存器里面的值,(此时我任然选的是最高优化级别)我的是4.7版本的MDK。
   于是我又重复了1里面说的,发现只要while里面有东西,哪怕简单得足以让可以保存unIdleCount的值,但是他也会每次都读取最新的值,
   于是我真的有点感慨,真的很智能,(他的理念或许就是这样),完全没意义的事就不用每次都浪费时间和代码空间读取最新的值了,但是
    真的有事要做的话就得严谨地每次读取最新的值看是不是满足条件,应不应该执行了,哈哈,说的优点多
7762642422d
3楼-- · 2019-07-16 06:42
本帖最后由 moyanming2013 于 2017-5-12 14:09 编辑

1.首先,强烈建议按如下规则执行定义和赋值(越是新的编译器越是会消弱这种规则,比如MDK-ARM V5):1.1.在块开始的地方,首先定义变量,既在做任何赋值运算前定义完所有变量!
1.2.注意是块,没说是一定是函数头,但函数头确实是一个块开始的地方。另外,凡是左花括号“{开始的地方都是块开始的地方。
在块开始的地方先定义变量,再执行赋值语句。
2.如下图:

uCount是定义时给了初值,这是定义不是赋值。uTime是在定义时有个计算过程,usec不是确定的值,在翻译为汇编时必然有个计算过程,语法看起来是定义时给了初值,但其中有个计算过程,这个计算过程如果在定义uCount之前执行的话,就违反了上述的第1条。所以请按照第1条写代码。
3.如下图:
看为什么会出错(我的理解,可以讨论)!

在0x08024984地址处的代码是往r1写了0,然后在0x08024986地址处存入内存该r1=0;在0x08024988地址处执行了r1+=1;此时r1=1,接着在0x0802498a地址处存入内存该r1,此时存入内存内r1=1;然后在0x0802498c地址处判断r1和r0,r0既是uTime的值;显然r1小于r0,在0x0802498e地址处跳到了0x08024988地址处,此时r1=2,再在0x0802498a地址处存入内存和正确的汇编代码比较后你会发现此错误的汇编总是存入内存而没有从内存读!没有读内存也没什么问题,因为r1总是正确的加1了,只是缺少了一个从内存中读出r1再加的过程,这个看似“多此一举”的动作为什么会那么重要呢?
显然“错误的汇编没有按照__IO(既:volatile)的属性来处理r1(只完成了一半:存)!
按照volatile的规则,每次都需要从ram中(而不是寄存器中)读写数据,那么按此规则执行一定是正确的,否则可能会出现问题。
楼主可以再看下,该delay函数退出时r1的值是多少?以及谁会打断该函数?


最后,建议使用最新的编译器和ST官方的代码!!
HAL最新的usb-host中没有该USB_OTG_BSP_uDelay函数的使用,都是用的HAL_DELAY函数:

使用HAL_Delay:


aril1227
4楼-- · 2019-07-16 10:41
 精彩回答 2  元偷偷看……
7762642422d
5楼-- · 2019-07-16 14:52
aril1227 发表于 2019-1-4 12:59
解释的非常精辟,感谢高手指点,我还以为自己找到原因了,看来还得继续深究:)
1:USB_OTG_BSP_uDelay函数退出时uTime=0x33,uCount=0x34;
2:因为项目移植HAL库改动较多风险也较大,所以暂时沿用标准库。

显然的,你这么写后,47EE处开始的代码和其循环,总是把值从ram中取出,然后加,又存到了ram,最后比较,如此循环,这个是没问题的。
卿小知1
6楼-- · 2019-07-16 15:42
我想请教一下楼主  keil优化等级选择3稳定吗?产品中应该选几

一周热门 更多>