taxidriver 发表于 2009-7-9 22:31:31

GCC同一语句编译后转换为汇编语句并且出现错误,求救!

我写的是一段由flag控制流水灯左流右流的语句,同样的语句只是位移符号不同,编译结果总是不同
if(flag == LEFT){
            //_delay_ms(500);
            if (PORTB == 0x7f) PORTB = 0b11111110;
            else               PORTB = ~(~PORTB << 1);
      }else{
            //_delay_ms(500);
            if (PORTB == 0xfe) PORTB = 0b01111111;
            else               PORTB = ~(~PORTB >> 1);
      }

其中关键的两句
else               PORTB = ~(~PORTB << 1);
else               PORTB = ~(~PORTB >> 1);
只是一个位移符号差异,但是汇编出来的语句完全不同
如下
第一句是这样的
28:                   else               PORTB = ~(~PORTB << 1);
+0000005D:   8180      LDD       R24,Z+0      Load indirect with displacement
+0000005E:   E090      LDI       R25,0x00       Load immediate
+0000005F:   9580      COM       R24            One's complement
+00000060:   9590      COM       R25            One's complement
+00000061:   0F88      LSL       R24            Logical Shift Left
+00000062:   1F99      ROL       R25            Rotate Left Through Carry
+00000063:   9580      COM       R24            One's complement
+00000064:   8380      STD       Z+0,R24      Store indirect with displacement
+00000065:   CFEE      RJMP      PC-0x0011      Relative jump
而第二句却变成这样,明显它忽略了我的取反的运算,即使我把优化等级改到O0也没用
32:                   else               PORTB = ~(~PORTB >> 1);
+0000006B:   8180      LDD       R24,Z+0      Load indirect with displacement
+0000006C:   9586      LSR       R24            Logical shift right
+0000006D:   8380      STD       Z+0,R24      Store indirect with displacement
+0000006E:   CFE5      RJMP      PC-0x001A      Relative jump

第一次使用GCC,请指教

windy__xp 发表于 2009-7-9 23:19:58

1. 注意一下运算符号的优先级别,专门查了一下,位移运算 >> << 优先于取反~,当然可以直接优化为左右移。
   建议不知道优先级的运算,都用括号,建议除了加减乘除法,都用括号。
   PORTB = ~((~PORTB) >> 1);

   个人喜欢这样写:
    PORTB <<= 1;
    PORTB|= 1;

2. LDD这种指令用得比较少,对于IO寄存器,本来可以直接用IN OUT 指令的。COM指令取补码(取反加1)
   一个数据两次取补码等于自身,在第一句中,结果还是相当于左移一bit,只是没有优化而已。

taxidriver 发表于 2009-7-10 00:29:04

我有考虑到优先级问题,加了括号结果还是一样,我用0s级别再优化一次,得到的结果是这样的
28:                   else               PORTB = ~((~PORTB) << 1);
+00000057:   B388      IN      R24,0x18       In from I/O location
+00000058:   E090      LDI       R25,0x00       Load immediate
+00000059:   9580      COM       R24            One's complement
+0000005A:   9590      COM       R25            One's complement
+0000005B:   0F88      LSL       R24            Logical Shift Left
+0000005C:   1F99      ROL       R25            Rotate Left Through Carry
+0000005D:   9580      COM       R24            One's complement
+0000005E:   C007      RJMP      PC+0x0008      Relative jump

32:                   else               PORTB = ~((~PORTB) >> 1);
+00000064:   B388      IN      R24,0x18       In from I/O location
+00000065:   9586      LSR       R24            Logical shift right
+00000066:   BB88      OUT       0x18,R24       Out to I/O location
+00000067:   CFE6      RJMP      PC-0x0019      Relative jump

很明显,第一句汇编后的结果是正确的,真正做到取反-移位-再取反
而第二句加括号后,汇编结果总是直接将数值移位-输出
两句唯一的区别仅仅在于 左移位和右移位
按我一般的想法就是两者汇编出来的结果应该仅仅是在LSL,LSR的区别,其它应该一样才对啊,真是奇怪

taxidriver 发表于 2009-7-10 01:57:07

我在草稿上演算了一遍又一遍,算法肯定是没问题的
个人估计,导致 《 》两种不同的结果可能是这样的原因,<<其实相当于原来的数值翻一倍速,而>>等于数值除以二,编译器认为,在除法进行之前取反,进行除法后再取反,结果是等同于直接进行除法,所以两个~被优化掉了,不过实在是很怪异,怎么也搞不行

windy__xp 发表于 2009-7-10 13:06:07

我 1L 犯错误了。

    原因应该是这样的:<< >> 运算应该是带符号的,而不是逻辑左右移,睡觉,晚上再验证。

windy__xp 发表于 2009-7-10 21:05:54

编写了个程序,验证了一下。

int main( void )
{
    INT8U   i=0;
   
    volatile INT8U   x=0xFE;
        volatile INT8S   y=0xFE;
   
    while( 1 )
    {
      if( i & 0x01 ) {
            PORTB = ~((~PORTB)>>1);
            x = ~((~x)>>1);
            y = ~((~y)>>1);
      }      
      else {
            PORTB = ~((~PORTB)<<1);
            x = ~((~x)<<1);
            y = ~((~y)<<1);
      }
      i++;
    }
}



反汇编后:

29:                   PORTB = ~((~PORTB)>>1);
+00000064:   B185      IN      R24,0x05         In from I/O location
+00000065:   9586      LSR   R24            Logical shift right
+00000066:   B985      OUT   0x05,R24         Out to I/O location
30:                   x = ~((~x)>>1);
+00000067:   8189      LDD   R24,Y+1          Load indirect with displacement
+00000068:   9586      LSR   R24            Logical shift right
+00000069:   8389      STD   Y+1,R24          Store indirect with displacement
31:                   y = ~((~y)>>1);
+0000006A:   818A      LDD   R24,Y+2          Load indirect with displacement
+0000006B:   2799      CLR   R25            Clear Register
+0000006C:   FD87      SBRC    R24,7            Skip if bit in register cleared
+0000006D:   9590      COM   R25            One's complement
+0000006E:   9580      COM   R24            One's complement
+0000006F:   9590      COM   R25            One's complement
+00000070:   9595      ASR   R25            Arithmetic shift right
+00000071:   9587      ROR   R24            Rotate right through carry
+00000072:   C018      RJMP    PC+0x0019      Relative jump
34:                   PORTB = ~((~PORTB)<<1);
+00000073:   B185      IN      R24,0x05         In from I/O location
+00000074:   E090      LDI   R25,0x00         Load immediate
+00000075:   9580      COM   R24            One's complement
+00000076:   9590      COM   R25            One's complement
+00000077:   0F88      LSL   R24            Logical Shift Left
+00000078:   1F99      ROL   R25            Rotate Left Through Carry
+00000079:   9580      COM   R24            One's complement
+0000007A:   B985      OUT   0x05,R24         Out to I/O location
35:                   x = ~((~x)<<1);
+0000007B:   8189      LDD   R24,Y+1          Load indirect with displacement
+0000007C:   E090      LDI   R25,0x00         Load immediate
+0000007D:   9580      COM   R24            One's complement
+0000007E:   9590      COM   R25            One's complement
+0000007F:   0F88      LSL   R24            Logical Shift Left
+00000080:   1F99      ROL   R25            Rotate Left Through Carry
+00000081:   9580      COM   R24            One's complement
+00000082:   8389      STD   Y+1,R24          Store indirect with displacement
36:                   y = ~((~y)<<1);
+00000083:   818A      LDD   R24,Y+2          Load indirect with displacement
+00000084:   2799      CLR   R25            Clear Register
+00000085:   FD87      SBRC    R24,7            Skip if bit in register cleared
+00000086:   9590      COM   R25            One's complement
+00000087:   9580      COM   R24            One's complement
+00000088:   9590      COM   R25            One's complement
+00000089:   0F88      LSL   R24            Logical Shift Left
+0000008A:   1F99      ROL   R25            Rotate Left Through Carry
+0000008B:   9580      COM   R24            One's complement
+0000008C:   838A      STD   Y+2,R24          Store indirect with displacement
+0000008D:   5F2F      SUBI    R18,0xFF         Subtract immediate
+0000008E:   4F3F      SBCI    R19,0xFF         Subtract immediate with carry
+0000008F:   CFD2      RJMP    PC-0x002D      Relative jump
+00000090:   CFFF      RJMP    PC-0x0000      Relative jump
+00000091:   95FF9590    CALL    0x003F9590       Call subroutine
+00000093:   0F88      LSL   R24            Logical Shift Left
+00000094:   1F99      ROL   R25            Rotate Left Through Carry
+00000095:   9580      COM   R24            One's complement
+00000096:   838A      STD   Y+2,R24          Store indirect with displacement
+00000097:   5F2F      SUBI    R18,0xFF         Subtract immediate
+00000098:   4F3F      SBCI    R19,0xFF         Subtract immediate with carry
+00000099:   CFC9      RJMP    PC-0x0036      Relative jump
+0000009A:   CFFF      RJMP    PC-0x0000      Relative jump



查了一下资料。

关于c中的移位运算

1.移位运算的两个运算数必须是整形表达式,在运算数的两边完成整形提升,表达式整体的类型与提升后的左运算数相同。

2.左移位 exp1<<exp2的表达式使得exp1的位被按expr2指明的数目进行左移,并在低端移入0

3.右移位运算符>>与左移位运算符是不对称的。对左操作数进行移位取决于左操作数的类型:

(1)如果左操作数是无符号(或带符号的非负数),则左边移入0

(2)如果左操作数是带符号的负数,则实现者可以选择补0或者把左操作数最左边的位移入(常见的是填充符号位)。因此,左操作数为负的带符号数值,而右操作数为非0时,>>的应用是不可移植的。

4. 如果右操作数是负数,则移位运算(左移和右移)的结果是未定义的。

5.如果右操作数的值大于或等于左操作数的宽度(位数),则结果也是未定义的。如果右操作数为0,则结果等于左操作数。


-------------------------------
    所以,还是改改程序吧,这个平时没注意,又学会一点。

taxidriver 发表于 2009-7-11 00:23:09

我试着根据你的资料归纳下
有两点与这程序相关的:
1.移位运算的两个运算数必须是整形表达式,在运算数的两边完成整形提升,表达式整体的类型与提升后的左运算数相同。
3.(2)如果左操作数是带符号的负数,则实现者可以选择补0或者把左操作数最左边的位移入(常见的是填充符号位)。因此,左操作数为负的带符号数值,而右操作数为非0时,>>的应用是不可移植的。
汇编过程应该是这样的
1.假原先PORTB中的内容是0x78,将其提升为整型,0x0078
2.取反变成0xff87
3.右移一位,变成0xffc3或是0xefc3(根据“则实现者可以选择补0或者把左操作数最左边的位移入(常见的是填充符号位)”)
4.再取反,即是变成0x003c或0x803c,而再写入PORTB中,就是0x3c

得出这样的结果,和将0x78直接右移一位,变成0x3c是一样的,所以GCC编译将其优化掉了。
这样就解释得通,但我这样的解释是建立在这样一个假设上,即语句PORTB = ~((~PORTB)>>1); 中(~PORTB)>>1这一部分,是先考虑>>两边的数据类型的
而当我将这一语句拆成三句写,再进行验证时,发现程序烧进芯片后运行正常,没有出现合在一起那种情况

1.PORTB = ~PORTB;先对PORTB取反,假设原来是0x78,取反变成0x87
2.PORTB = PORTB >> 1;第二步本是移位运算,但PORTB在GCC中定义是unsigned char,所以必须先提升为int,此时变为0x0087
提升后进行移位,变成0x0043
3.PORTB = ~PORTB;再取反变成0xffbc,写入PORTB的是0xbc
得出的结果是我想要的,所以这个假设应该行得通

taxidriver 发表于 2009-7-11 00:38:49

感谢楼上的兄台,看来得自己多查查资料才是,avrfreaks上面有人就问同样的问题,看到另一种解决方法是
PORTB = ~(((unsigned char)(~PORTB))>>1),把左操作数的类型再强制转换过来就行
页: [1]
查看完整版本: GCC同一语句编译后转换为汇编语句并且出现错误,求救!