搜索
bottom↓
回复: 37

keil C51 V9.59以前的版本都可能存在一个“坑...

  [复制链接]

出0入0汤圆

发表于 2019-8-21 20:51:14 | 显示全部楼层 |阅读模式
本帖最后由 machao 于 2019-8-22 23:21 编辑

使用keil C51的朋友注意了,版本V9.59以前的所有版本都有一个“坑”!

在v9.59的 Release Notes 中有一个修正!主要是避免8位有符号数与无符号数比较出错的问题(在少数情况下会发生)

Corrected: in rare cases a signed compare with unsigned char treated the unsigned char as signed. This happens if the unsigned char value results from a calculated assignment. Example:
char i;
unsigned char uc0 = 254;
char c1 = 0;

void main() {
  if (c1 <= (uc0 += c1))    // uc0 is wrongly treated signed => -2 therefore the condition (0 <= -2) is false
    i = 1;
  else
    i = 0;
}

in rare cases  翻译成中文意思:在极少数情况下........

=======================================================
在c语言中是这样规定的:常量无unsigned型。但一个非负值的整数可以赋值给unsigned整型变量,只要它的范围不超过变量的取值范围即可。

换句话说,一个常量,比如10,它是一个signed型!

在编写程序时,我们可以有意识的避免两个不同类型(unsigned与signed)变量之间的比较,但会忽视一个变量与常量的比较,比如,经常使用如下的语句:

uchar i;

if (i >= 10 ) {}

for(i=0;i<10;i++){}

尽管定义了变量 i 为unsigned,但常量10却为signed。所以上面两句语句都是不同类型的比较。按keil c51 V9.59的 Release Notes中说法,在“极少情况下”会出现错误的。

我查了一下,keil c51 V9.59是2018年4月推出的,说明在以前的版本中,“极少情况下”会出现错误的结果。

keil c51 V9.59以及以后的版本(V9.60、V9.60a)做了修正,防止了这种“极少情况下”会出现错误的现象发生,但其编译后的代码要比以前版本的长。

目前解决的办法有:
1/ 不考虑代码效率时,用V9.59以后的版本重新编译。
2/ 要考虑代码效率时,按keil c51 V9.59的 Release Notes中说法,使用强制转换,包括对常量。上面的例子改成:

uchar i;

if (i >= (uchar)10 ) {}

for(i=0;i<(uchar)10;i++){}

出0入0汤圆

 楼主| 发表于 2019-8-21 20:52:47 | 显示全部楼层
这段英文原文如下:

Corrected: in rare cases a signed compare with unsigned char treated the unsigned char as signed. This happens if the unsigned char value results from a calculated assignment. Example:
char i;
unsigned char uc0 = 254;
char c1 = 0;

void main() {
  if (c1 <= (uc0 += c1))    // uc0 is wrongly treated signed => -2 therefore the condition (0 <= -2) is false
    i = 1;
  else
    i = 0;
}
Wrong code was generated:
0000 E500        R     MOV     A,uc0
0002 2500        R     ADD     A,c1
0004 F500        R     MOV     uc0,A
0006 D3                SETB    C
0007 6480              XRL     A,#080H       \
0009 F8                MOV     R0,A          |
000A E500        R     MOV     A,c1          |
000C 6480              XRL     A,#080H       |
000E 98                SUBB    A,R0          |
000F 5004              JNC     ?C0001        -> signed char compare
0011 750001      R     MOV     i,#01H
0014 22                RET
0015         ?C0001:
0015 E4                CLR     A
0016 F500        R     MOV     i,A
0018         ?C0003:
0018 22                RET
The correct code is much longer because according to the C standard a cast to int is necessary:
0000 E500        R     MOV     A,uc0
0002 2500        R     ADD     A,c1
0004 FF                MOV     R7,A
0005 F500        R     MOV     uc0,A
0007 AD00        R     MOV     R5,c1         \
0009 ED                MOV     A,R5          |
000A 33                RLC     A             |
000B 95E0              SUBB    A,ACC         |
000D FC                MOV     R4,A          -> cast of signed char to int
000E D3                SETB    C             \
000F ED                MOV     A,R5          |
0010 9F                SUBB    A,R7          |
0011 7480              MOV     A,#080H       +> implicit cast of unsigned char to int
0013 F8                MOV     R0,A          |
0014 6C                XRL     A,R4          |
0015 98                SUBB    A,R0          |
0016 5004              JNC     ?C0001        -> signed int compare
0018 750001      R     MOV     i,#01H
001B 22                RET
001C         ?C0001:
001C E4                CLR     A
001D F500        R     MOV     i,A
001F         ?C0003:
001F 22                RET
To generate smaller code the signedness of the values to be compared should be the same:
...
        if ((unsigned char)c1 <= (uc0 += c1))
...
0000 E500        R     MOV     A,uc0
0002 2500        R     ADD     A,c1
0004 FF                MOV     R7,A
0005 F500        R     MOV     uc0,A
0007 E500        R     MOV     A,c1
0009 D3                SETB    C             \
000A 9F                SUBB    A,R7          |
000B 5004              JNC     ?C0001        -> unsigned char compare
000D 750001      R     MOV     i,#01H
0010 22                RET
0011         ?C0001:
0011 E4                CLR     A
0012 F500        R     MOV     i,A
0014         ?C0003:
0014 22                RET

出0入0汤圆

发表于 2019-8-21 21:22:33 | 显示全部楼层
这确实是非常严重的错误,而且比较底层

出10入120汤圆

发表于 2019-8-21 21:27:43 来自手机 | 显示全部楼层
我自己一般不把有符号和无符号数直接比较,一直担心这个问题,果然是雷。

出0入4汤圆

发表于 2019-8-21 21:30:39 来自手机 | 显示全部楼层
这个坑有点大。

出0入0汤圆

发表于 2019-8-21 21:55:15 | 显示全部楼层
这么。。。。大的坑。

出0入0汤圆

 楼主| 发表于 2019-8-22 01:14:42 | 显示全部楼层
makesoft 发表于 2019-8-21 21:27
我自己一般不把有符号和无符号数直接比较,一直担心这个问题,果然是雷。 ...

以前没有注意到这一点,好在这不是每次比较都会出现问题。但总是不放心。
如果程序中是两个变量(一个有符号,一个无符号)做比较时,还能够注意到。但一个无符号变量与常数比较时,就会忽视。

按说明中,要加强制转换,才能生成比较简洁的汇编代码,否则代码会增加的。

我在另外一个帖子中有例子。现在要这样写代码,常数也要强制转换,才能生成简洁代码。

// time0 中断服务,中断时间为50us, 运行模式12T
       uchar time_1k_counter,time_100hz_counter,time_10hz_counter;
       bit time_key_ok;

      void Int_time0(void) interrupt T0_VECTOR
      {
        if(++time_1k_counter >= (uchar)10)
        {
          clk_1k = !clk_1k;
          time_1k_counter = 0;
          if (++time_100hz_counter >= (uchar)10)
          {
            clk_100hz = !clk_100hz;
            time_100hz_counter = 0;
            time_key_ok = true;
            if (++time_10hz_counter >= (uchar)10)
            {
              clk_10hz = !clk_10hz;
              time_10hzcounter = 0;
            }
          }
       }
    }

出0入0汤圆

发表于 2019-8-22 01:22:42 来自手机 | 显示全部楼层
这个确实坑

出30入25汤圆

发表于 2019-8-22 02:22:10 | 显示全部楼层
这类比较不会有警告吗?或者有没有办法严格调整编译器设置,要求编译器对这种行为发出警告?

出0入0汤圆

发表于 2019-8-22 08:15:00 | 显示全部楼层
很多年以前在PIC的编译器中遇过,无符号与有符号比较时,无符号数恒大于有符号数,至此以后再也不这么写了。没想到keil也有类似bug

出0入0汤圆

发表于 2019-8-22 08:38:43 | 显示全部楼层
不是编译器 bug,是编程者自身bug,为什么要混用?查错都不方便,显性转换或者不混合比较不好吗?

出0入0汤圆

发表于 2019-8-22 08:44:14 | 显示全部楼层
本帖最后由 lcw_swust 于 2019-8-22 08:46 编辑

确实,我昨天也被坑了。
无符号数与有符号数比较,有符号数会被当作无符号数,比如 -1 会被当作 255.
(似乎后方参数会转换成前方参数的类型)

出0入0汤圆

发表于 2019-8-22 09:35:38 | 显示全部楼层
比较大小必须同类型啊,加强制转换应该也不会出错吧。

出0入0汤圆

发表于 2019-8-22 10:22:37 | 显示全部楼层
感谢,学习了!

出0入0汤圆

发表于 2019-8-22 10:36:21 | 显示全部楼层
那意思是升级到V9.59或以上版本就没事咯

出0入0汤圆

发表于 2019-8-22 10:56:34 | 显示全部楼层
是0x80那个bug吗?记得以前有个补丁的

出0入0汤圆

 楼主| 发表于 2019-8-22 12:05:06 | 显示全部楼层
在c语言中是这样规定的:常量无unsigned型。但一个非负值的整数可以赋值给unsigned整型变量,只要它的范围不超过变量的取值范围即可。

换句话说,一个常量,比如10,它是一个signed型!

在编写程序时,我们可以有意识的避免两个不同类型(unsigned与signed)变量之间的比较,但会忽视一个变量与常量的比较,比如,经常使用如下的语句:

uchar i;

if (i >= 10 ) {}

for(i=0;i<10;i++){}

尽管定义变量i时unsigned,但常量10却为signed。所以上面两句语句都是不同类型的比较。按keil c51 V9.59的 Release Notes中说法,在“极少情况下”会出现错误的。

我查了一下,keil c51 V9.59是2018年4月推出的,说明在以前的版本中,“极少情况下”会出现错误的结果。

keil c51 V9.59以及以后的版本(V9.60、V9.60a)做了修正,防止了这种“极少情况下”会出现错误的现象发生,但其编译后的代码要比以前版本的长。

目前解决的办法有:
1/ 不考虑代码效率时,用V9.59以后的版本重新编译。
2/ 要考虑代码效率时,按keil c51 V9.59的 Release Notes中说法,使用强制转换,包括对常量。上面的例子改成:

uchar i;

if (i >= (uchar)10 ) {}

for(i=0;i<(uchar)10;i++){}

出25入12汤圆

发表于 2019-8-22 13:04:12 | 显示全部楼层
感谢楼主, 无符号用的多 有符号经常用于显示,碰巧避开了

出0入0汤圆

发表于 2019-8-22 13:58:49 | 显示全部楼层
感谢!51中平时都是有符号的,M0以上无符号较多,算法难免。MDK有这个bug么?

出0入0汤圆

发表于 2019-8-22 14:32:24 | 显示全部楼层
这个BUG不小,

出330入0汤圆

发表于 2019-8-22 14:54:32 来自手机 | 显示全部楼层
machao 发表于 2019-8-22 12:05
在c语言中是这样规定的:常量无unsigned型。但一个非负值的整数可以赋值给unsigned整型变量,只要它的范围 ...

好麻烦,与常数比较用的可多了。

出0入0汤圆

发表于 2019-8-22 15:32:48 | 显示全部楼层
machao 发表于 2019-8-22 01:14
以前没有注意到这一点,好在这不是每次比较都会出现问题。但总是不放心。
如果程序中是两个变量(一个有 ...

我写的代码编译后HEX文件太大了,用这个会不会小点?

出0入0汤圆

发表于 2019-8-22 17:55:28 | 显示全部楼层
  嗯,印象中用 Keil C51 编程,程序得规范点,这样出错概率低一些。像比较、运算这类,变量类型不一样的,尽量加上强制转换。不然编译中也没有提示,回头查错比较头痛。
  IAR的好像更智能,这方面可以宽松一点

出10入10汤圆

发表于 2019-8-22 19:18:35 | 显示全部楼层
马老师一出山就是重量级的提醒,感谢!

出0入95汤圆

发表于 2019-8-22 19:29:54 | 显示全部楼层
多谢提醒,才留意到这种情况。

出0入76汤圆

发表于 2019-8-22 21:49:08 | 显示全部楼层
本帖最后由 foxpro2005 于 2019-8-22 22:20 编辑

感谢马老师提醒,还真没注意到这个问题呢。

这个似乎是 unsigned 与 signed 类型在比较、计算时,默认类型转换的问题吧
KEIL C51编译器把 一个signed char 与 一个 unsigned char 数 在比较时,都转换成 signed处理了?

出0入0汤圆

发表于 2019-8-26 11:29:10 | 显示全部楼层
KEILl软件 2020快到期了,以后怎么办?

出110入8汤圆

发表于 2019-8-26 11:54:56 | 显示全部楼层
习惯会加上强制类型转换

出0入8汤圆

发表于 2019-8-26 13:55:23 | 显示全部楼层
感谢老师的提醒,以前真没注意,下次改过来

出0入0汤圆

发表于 2019-8-27 08:51:23 | 显示全部楼层
感谢提醒

出0入0汤圆

发表于 2019-8-27 11:26:14 | 显示全部楼层
不要在判断里做运算,在车规里这些都是不合规的。有些人追求所谓的简短,工程上要的是健壮

出1310入193汤圆

发表于 2019-8-27 11:31:35 | 显示全部楼层
skystalker 发表于 2019-8-27 11:26
不要在判断里做运算,在车规里这些都是不合规的。有些人追求所谓的简短,工程上要的是健壮 ...

愿闻其详       经常这样做  经过你提醒今后需要改这个习惯了  

出0入17汤圆

发表于 2019-8-27 11:40:26 来自手机 | 显示全部楼层
用于ARM的MDK没有这类的问题吧

出0入0汤圆

发表于 2020-3-11 11:10:04 | 显示全部楼层
谢谢!

出0入0汤圆

发表于 2021-8-9 18:51:02 | 显示全部楼层
好大的坑,还好不同类型的习惯加上强制类型转换

出0入4汤圆

发表于 2021-8-9 21:31:03 | 显示全部楼层
我前段时间也中招了,用一个signed 数与 常数比较
MDK 也存在这个问题,在常数前加上强转就ok了

出0入0汤圆

发表于 2021-8-20 03:39:51 | 显示全部楼层
看来一律加上强制类型转换确实是个好习惯
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-19 15:34

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表