打印

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

[复制链接]
10411|22
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
最近写程序时遇到一个问题,用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的计算结果会异常?是编译环境设置的问题吗?盼高手指点一下。谢谢!

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

void main(void)
{
  sumvalue = addend0*0x1000000+addend1*0x10000+addend2*0x100+addend3;
  
  sumvalue1 = addend00*0x1000000+addend01*0x10000+addend02*0x100+addend03;
  
  sumvalue2 = addend0*(unsigned long)0x1000000+addend1*(unsigned long)0x10000+addend2*(unsigned long)0x100+addend3;
  
  sumvalue3 = addend00*(unsigned long)0x1000000+addend01*(unsigned long)0x10000+addend02*(unsigned long)0x100+addend03;
  
  addend0 = sumvalue2/0x1000000;
  
  addend1 = sumvalue2/0x10000%0x100;
  
  addend2 = sumvalue2/0x100%0x100;
  
  addend3 = sumvalue2%0x100;
  
  addend00 = sumvalue3/0x1000000;
  
  addend01 = sumvalue3/0x10000%0x100;
  
  addend02 = sumvalue3/0x100%0x100;
  
  addend03 = sumvalue3%0x100;
  
  //intvalue = addend00*(unsigned int)0x100+addend01;
  
  //intvalue1 = addend00*0x100+addend01;
  
  while(1);
}

相关帖子

沙发
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 | 只看该作者
做技术就是需要这种钻研的精神

使用特权

评论回复
5
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;

使用特权

评论回复
6
jamie-ma| | 2012-2-6 14:19 | 只看该作者
楼主在做嵌入式吗?

使用特权

评论回复
7
jayy| | 2012-2-6 20:30 | 只看该作者
藏龙卧虎啊 拜读了呢

使用特权

评论回复
8
jayy| | 2012-2-6 20:30 | 只看该作者
都是高级工程师?

使用特权

评论回复
9
caiwenbin| | 2012-2-6 21:52 | 只看该作者
拜读

使用特权

评论回复
10
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标准不熟的朋友)可得小心!

使用特权

评论回复
11
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在哪里看到您说的这个规则,这回印象深刻了,呵呵。

使用特权

评论回复
12
永远的不知| | 2012-2-8 13:39 | 只看该作者
11# yirongfu

有时候规范的写程序还是非常重要的,不然会有一些莫名奇妙的问题等着你去解决。

使用特权

评论回复
13
yirongfu|  楼主 | 2012-2-8 17:19 | 只看该作者
12# 永远的不知

严重赞同!!:)

使用特权

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

-----------------测试代码-----------------
unsigned char addend00=0xff;
unsigned char addend01=0x00;
unsigned char addend02=0xff;
unsigned char addend03=0xff;
unsigned long sumvalue,sumvalue1,sumvalue2,sumvalue3,sumvalue0;


void main(void)
{
   
  sumvalue1 = addend00*0x1000000;   //4278190080(0xFF000000)
  
  sumvalue2 = addend01*0x10000;     //0
  
  sumvalue3 = addend02*0x100;       //4294967040(0xFFFFFF00)
  
  sumvalue0 = sumvalue1 + sumvalue2 + sumvalue3;  //4278189824(0xFEFFFF00)
  
  sumvalue0 = sumvalue1 + sumvalue2 + sumvalue3 + addend03;  //4278190079(0xFEFFFFFF)
  
  sumvalue = addend00*0x1000000 + addend01*0x10000 + addend02*0x100 + addend03;  //4278190079
  
  while(1);
}

使用特权

评论回复
15
uc_stm32f050| | 2012-2-9 09:29 | 只看该作者
如果符号位,也只是最高位(bit)为1吧?
-------------------------------------------------------------------
NO。

有符号数表示使用补码表示的。
你先理解一下什么是补码。

使用特权

评论回复
16
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

使用特权

评论回复
17
uc_stm32f050| | 2012-2-9 09:57 | 只看该作者
本帖最后由 uc_stm32f050 于 2012-2-9 09:59 编辑

总之:
C语言里面的运算,必须要考虑类型。
C语言所有变量,常量的运算都是  值与类型 共同运算的结果。

使用特权

评论回复
18
TI_MCU| | 2012-2-9 10:42 | 只看该作者
自己写代码试了下,在IAR V6.21和V6.20下,
sumvalue3 = addend02*0x100的结果就是0xFF00,而不是楼主说的0xFFFFFF00。
建议楼主看看自己的汇编代码,到底是怎么计算的。看楼主的代码,如果真这么写,计算结果很有可能被优化掉而直接存入结果的。

一般来说,现在的编译器对类型转换时的符号问题都处理得不错,建议楼主及时升级

使用特权

评论回复
19
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
20
yirongfu|  楼主 | 2012-2-10 01:00 | 只看该作者
非常感谢uc_stm32f050和版主的解答,尤其是uc_stm32f050的详解!!!uc_stm32f050功力深厚:D

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

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

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

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

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

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

主题

914

帖子

2

粉丝