同样的代码在IAR和Keil C51中仿真结果不一样

[复制链接]
 楼主| yirongfu 发表于 2012-2-3 15:31 | 显示全部楼层 |阅读模式
最近写程序时遇到一个问题,用IAR Embedded Workbench Evaluation for MSP430 4.20和Keil uVision2 V2.40(C Compiler版本是V7.09)分别进行编译并软仿真同样的代码(附后),结果却不一样。两个开发环境均为默认设置,Keil选择的器件是中颖的SH88F54,IAR选择的器件是Generic MSP430 device,使用软件仿真。问题出现在IAR中,用unsigned char变量(取一维数组中的元素)与常数相乘,并将结果赋值给unsigned long变量,对于某些数组元素值,结果会出错。详情如下:

(1)如下代码所示,假设一组数为{addend00=0xFF,addend01=0xFF,addend02=0xFF,addend03=0xFF},在Keil中,sumvalue1的计算结果是
0xFFFFFFFF,这是对的,而IAR中,sumvalue1的计算结果却是0xFFFEFFFF(即十进制4294901759),显然不是预想的值;


(2)乘数为{addend0=0x12,addend1=0x34,addend2=0x56,addend3=0x78}时,Keil和IAR的计算结果一样,都是正确的,即0x12345678;

(3)乘数为{addend00=0xFF,addend01=0x00,addend02=0xFF,addend03=0xFF}时,在Keil中,sumvalue1的计算结果是
0xFF00FFFF,而IAR中,sumvalue1的计算结果却是0xFEFFFFFF(即十进制4278190079),也不对。



(4)后来将乘加式中的常数加上类型转换(unsigned long),上述问题就解决了,即下述代码中的sumvalue3。

    不知这是什么原因,为何IAR的计算结果会异常?是编译环境设置的问题吗?盼高手指点一下。谢谢!

-------------------------程序代码-------------------------
  1. unsigned char addend0=0x12,addend00=0xff;
  2. unsigned char addend1=0x34,addend01=0x00;
  3. unsigned char addend2=0x56,addend02=0xff;
  4. unsigned char addend3=0x78,addend03=0xff;
  5. unsigned long sumvalue,sumvalue1,sumvalue2,sumvalue3;
  6. //unsigned int intvalue,intvalue1;

  7. void main(void)
  8. {
  9.   sumvalue = addend0*0x1000000+addend1*0x10000+addend2*0x100+addend3;
  10.   
  11.   sumvalue1 = addend00*0x1000000+addend01*0x10000+addend02*0x100+addend03;
  12.   
  13.   sumvalue2 = addend0*(unsigned long)0x1000000+addend1*(unsigned long)0x10000+addend2*(unsigned long)0x100+addend3;
  14.   
  15.   sumvalue3 = addend00*(unsigned long)0x1000000+addend01*(unsigned long)0x10000+addend02*(unsigned long)0x100+addend03;
  16.   
  17.   addend0 = sumvalue2/0x1000000;
  18.   
  19.   addend1 = sumvalue2/0x10000%0x100;
  20.   
  21.   addend2 = sumvalue2/0x100%0x100;
  22.   
  23.   addend3 = sumvalue2%0x100;
  24.   
  25.   addend00 = sumvalue3/0x1000000;
  26.   
  27.   addend01 = sumvalue3/0x10000%0x100;
  28.   
  29.   addend02 = sumvalue3/0x100%0x100;
  30.   
  31.   addend03 = sumvalue3%0x100;
  32.   
  33.   //intvalue = addend00*(unsigned int)0x100+addend01;
  34.   
  35.   //intvalue1 = addend00*0x100+addend01;
  36.   
  37.   while(1);
  38. }

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
uc_stm32f050 发表于 2012-2-3 16:13 | 显示全部楼层
本帖最后由 uc_stm32f050 于 2012-2-3 16:37 编辑

楼主你搞反了,其实是IAR对了,KEIL错了。
或者说,IAR在运算时,遵守标准C,而KEIL却没有遵守标准C。

用AVRGCC和SDCC51实验,结果与IAR一样。

下面几个式子,楼主看看  
(假设int是16位,运算规则采用标准C)

((unsigned char)0xFF)*0x10000   
((unsigned char)0xFF)*0x100   

((unsigned char)0xFF)*0x10000U   
((unsigned char)0xFF)*0x100U   

((unsigned char)0xFF)*0x10000L
((unsigned char)0xFF)*0x100L

((unsigned char)0xFF)*0x1000000


结果的值和类型.
老电工1979 发表于 2012-2-3 22:15 | 显示全部楼层
楼上果然是高手!!
tianm 发表于 2012-2-4 09:03 | 显示全部楼层
做技术就是需要这种钻研的精神
TI_MCU 发表于 2012-2-6 10:04 | 显示全部楼层
IAR的结果是对的。在标准C算式中有一个原则,即两个不同类型的数值计算时,会换算成比较大的类型进行计算。比方说addend00*0x1000000中,addend00是unsigned char而0x1000000会是signed long类型,所以计算时,addend00的值也会转换成signed long进行计算,所以在算sumvalue1时,实际上是
    sumvalue1 = signed long + signed long + signed long + signed long
这里就存在溢出及符号改变的问题了。即使结果正确,这么做也存在风险。

根据楼主的应用,自然是应该使用:
    sumvalue = unsigned long + unsigned long + unsigned long + unsigned long

所以楼主按下面这样写才是最保险的:
    sumvalue3 = addend00*(unsigned long)0x1000000+addend01*(unsigned long)0x10000+addend02*(unsigned long)0x100+addend03;

或者写成:
    sumvalue3 = addend00*0x1000000UL+addend01*0x10000UL+addend02*0x100UL+addend03;
jamie-ma 发表于 2012-2-6 14:19 | 显示全部楼层
楼主在做嵌入式吗?
jayy 发表于 2012-2-6 20:30 | 显示全部楼层
藏龙卧虎啊 拜读了呢
jayy 发表于 2012-2-6 20:30 | 显示全部楼层
都是高级工程师?
caiwenbin 发表于 2012-2-6 21:52 | 显示全部楼层
拜读
 楼主| yirongfu 发表于 2012-2-7 16:29 | 显示全部楼层
楼主你搞反了,其实是IAR对了,KEIL错了。
或者说,IAR在运算时,遵守标准C,而KEIL却没有遵守标准C。

用AVRGCC和SDCC51实验,结果与IAR一样。

下面几个式子,楼主看看  
(假设int是16位,运算规则采用标准C)

((u ...
uc_stm32f050 发表于 2012-2-3 16:13


感谢指正!!
是的,我又用意法的STVD仿真了一下,使用STM8 Cosmic,软件仿真,结果和IAR一样。
使用Keil的朋友(尤其是像我一样不常写程序、对C标准不熟的朋友)可得小心!
 楼主| yirongfu 发表于 2012-2-7 16:33 | 显示全部楼层
IAR的结果是对的。在标准C算式中有一个原则,即两个不同类型的数值计算时,会换算成比较大的类型进行计算。比方说addend00*0x1000000中,addend00是unsigned char而0x1000000会是signed long类型,所以计算时,adden ...
TI_MCU 发表于 2012-2-6 10:04


多谢版主,省得我再去费时间查资料了:victory:;P
印象中好像long long ago在哪里看到您说的这个规则,这回印象深刻了,呵呵。
永远的不知 发表于 2012-2-8 13:39 | 显示全部楼层
11# yirongfu

有时候规范的写程序还是非常重要的,不然会有一些莫名奇妙的问题等着你去解决。
 楼主| yirongfu 发表于 2012-2-8 17:19 | 显示全部楼层
12# 永远的不知

严重赞同!!:)
 楼主| yirongfu 发表于 2012-2-8 17:32 | 显示全部楼层
我又进一步用下述代码在IAR下测试了下(“//”后面的数值是仿真结果),想知道原来的问题与自己的设计到底在哪里有了出入,遇到一个新的疑问:代码中sumvalue3的值仿真结果是0xFFFFFF00,这个值我本以为应该是0x0000FF00,但它怎么高位都是F了呢?如果符号位,也只是最高位(bit)为1吧?不知道像sumvalue3这个算式在运算时是怎么处理的?规则是怎样的?求解!

-----------------测试代码-----------------
  1. unsigned char addend00=0xff;
  2. unsigned char addend01=0x00;
  3. unsigned char addend02=0xff;
  4. unsigned char addend03=0xff;
  5. unsigned long sumvalue,sumvalue1,sumvalue2,sumvalue3,sumvalue0;


  6. void main(void)
  7. {
  8.    
  9.   sumvalue1 = addend00*0x1000000;   //4278190080(0xFF000000)
  10.   
  11.   sumvalue2 = addend01*0x10000;     //0
  12.   
  13.   sumvalue3 = addend02*0x100;       //4294967040(0xFFFFFF00)
  14.   
  15.   sumvalue0 = sumvalue1 + sumvalue2 + sumvalue3;  //4278189824(0xFEFFFF00)
  16.   
  17.   sumvalue0 = sumvalue1 + sumvalue2 + sumvalue3 + addend03;  //4278190079(0xFEFFFFFF)
  18.   
  19.   sumvalue = addend00*0x1000000 + addend01*0x10000 + addend02*0x100 + addend03;  //4278190079
  20.   
  21.   while(1);
  22. }
uc_stm32f050 发表于 2012-2-9 09:29 | 显示全部楼层
如果符号位,也只是最高位(bit)为1吧?
-------------------------------------------------------------------
NO。

有符号数表示使用补码表示的。
你先理解一下什么是补码。
uc_stm32f050 发表于 2012-2-9 09:36 | 显示全部楼层
本帖最后由 uc_stm32f050 于 2012-2-9 12:27 编辑

sumvalue3 = addend02*0x100;       //4294967040(0xFFFFFF00)
---------------------------------------------------------------------------------------
分析:
addend02的值是0xff,类型是unsigned char
字面值常数0x100的值是0x100,类型是int
((unsigned char)0xff)*((int)(0x100))  结果值是0XFF00,类型是int  (对应10进制为-256)
即:
addend02*0x100
=((unsigned char)0xff)*((int)(0x100))
=(int)(0xff00)
=(int)(-256)

sumvalue3的类型是unsigned long
sumvalue3=(unsigned long)((int)(-256))
=(unsigned long)(-256)
=(unsigned long)(0xffffff00)
结果值为0xFFFFFF00,类型是unsigned long
uc_stm32f050 发表于 2012-2-9 09:57 | 显示全部楼层
本帖最后由 uc_stm32f050 于 2012-2-9 09:59 编辑

总之:
C语言里面的运算,必须要考虑类型。
C语言所有变量,常量的运算都是  值与类型 共同运算的结果。
TI_MCU 发表于 2012-2-9 10:42 | 显示全部楼层
自己写代码试了下,在IAR V6.21和V6.20下,
sumvalue3 = addend02*0x100的结果就是0xFF00,而不是楼主说的0xFFFFFF00。
建议楼主看看自己的汇编代码,到底是怎么计算的。看楼主的代码,如果真这么写,计算结果很有可能被优化掉而直接存入结果的。

一般来说,现在的编译器对类型转换时的符号问题都处理得不错,建议楼主及时升级
uc_stm32f050 发表于 2012-2-9 12:35 | 显示全部楼层
本帖最后由 uc_stm32f050 于 2012-2-9 13:38 编辑
自己写代码试了下,在IAR V6.21和V6.20下,
sumvalue3 = addend02*0x100的结果就是0xFF00,而不是楼主说的0xFFFFFF00。
建议楼主看看自己的汇编代码,到底是怎么计算的。看楼主的代码,如果真这么写,计算结果很有可 ...
TI_MCU 发表于 2012-2-9 10:42


楼上你用的是IARARM,int是32位。
而IARAVR,IARMSP430,IAR8051 int都是16位。
由于int的位数不同,得出的结果也会迥异。

如果int是16位:
addend02*0x100
=((unsigned char)0xff)*((int)(0x100))
=(int)(0xff00)    //int是16位,(int)(0xff00)是负数, 表示的十进制是-256
=(int)(-256)
sumvalue3的类型是unsigned long
sumvalue3=(unsigned long)((int)(-256))
=(unsigned long)(-256)
=(unsigned long)(0xffffff00)
结果值为0xffffff00,类型是unsigned long

如果int是32位:
addend02*0x100
=((unsigned char)0xff)*((int)(0x100))
=(int)(0xff00)    //int是32位,(int)(0xff00) 是正数,表示的十进制是255
=(int)(255)
sumvalue3的类型是unsigned long
sumvalue3=(unsigned long)((int)(255))
=(unsigned long)(255)
=(unsigned long)(0x0000ff00)
结果值为0x0000ff00,类型是unsigned long

IARARM int是32位,类似也有
unsigned long long sumvalue3;
sumvalue3 = 0xff*0x1000000 ;
结果是0xFFFFFFFFFF000000
而不是0x00000000FF000000

还是那句老话:
C语言所有变量,常量的运算都是  值与类型 共同运算的结果。

评分

参与人数 1威望 +2 收起 理由
TI_MCU + 2

查看全部评分

 楼主| yirongfu 发表于 2012-2-10 01:00 | 显示全部楼层
非常感谢uc_stm32f050和版主的解答,尤其是uc_stm32f050的详解!!!uc_stm32f050功力深厚:D

对比了下汇编代码,发现Keil和IAR的处理方式差异很大,Keil的处理方法似乎太“优化”了些,感觉是先判断了乘数0x100,然后再相应地用最少的代码实现,汇编代码摘录如下:
----------Keil-----------

  1.     15:   sumvalue3 = addend02*0x100;       //4294967040(0xFFFFFF00)
  2.     16:   
  3. C:0x002F    E51A     MOV      A,addend02(0x1A)
  4. C:0x0031    FE       MOV      R6,A
  5. C:0x0032    E4       CLR      A
  6. C:0x0033    F517     MOV      0x17,A
  7. C:0x0035    8E16     MOV      0x16,R6
  8. C:0x0037    F515     MOV      0x15,A
  9. C:0x0039    F514     MOV      sumvalue3(0x14),A

寄存器0x14-17应该是用来存放sumvalue3的,这里就不涉及逻辑运算,只是寄存器和内存的数据搬移和清零

----------IAR-----------

  1.   sumvalue3 = addend02*0x100;       //4294967040(0xFFFFFF00)
  2. 000274   425E 0201         mov.b   &addend02,R14
  3. 000278   4E4E              mov.b   R14,R14
  4. 00027A   F03E 00FF         and.w   #0xFF,R14
  5. 00027E   108E              swpb    R14
  6. 000280   4E0F              mov.w   R14,R15
  7. 000282   E33F              inv.w   R15
  8. 000284   5F0F              rla.w   R15
  9. 000286   7F0F              subc.w  R15,R15
  10. 000288   4E82 0210         mov.w   R14,&sumvalue3
  11. 00028C   4F82 0212         mov.w   R15,&0x212
  12.   

R14和R15用来存放sumvalue3,应该就是uc_stm32f050所解释的关键。

现在我感觉它们的数据类型都是标准的,只是在运算或者存储时的处理规则不一样,可能正如uc_stm32f050所言,Keil的处理方法有点特别。

另外,查了下第一帖中提到的两个开发环境下的C编译帮助文档,确实就像uc_stm32f050所说的,IAR for MSP430中int是16bit,IAR for ARM是32bit,Keil中是16bit,不过有一点值得注意,IAR中都提到了负数采用补码形式,而keil中并未提及(或者我还没找到),截图如下(IAR for ARM的帮助文档是IAR Embedded Workbench for ARM 5.30 Kickstart环境下的):
----IAR for ARM

----IAR for 430

----KEIL C51

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:生活将我们磨圆,是为了让我们滚得更远。。。 我来到这个世上就没打算活着回去!

99

主题

918

帖子

2

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