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,请指教 1. 注意一下运算符号的优先级别,专门查了一下,位移运算 >> << 优先于取反~,当然可以直接优化为左右移。
建议不知道优先级的运算,都用括号,建议除了加减乘除法,都用括号。
PORTB = ~((~PORTB) >> 1);
个人喜欢这样写:
PORTB <<= 1;
PORTB|= 1;
2. LDD这种指令用得比较少,对于IO寄存器,本来可以直接用IN OUT 指令的。COM指令取补码(取反加1)
一个数据两次取补码等于自身,在第一句中,结果还是相当于左移一bit,只是没有优化而已。 我有考虑到优先级问题,加了括号结果还是一样,我用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的区别,其它应该一样才对啊,真是奇怪 我在草稿上演算了一遍又一遍,算法肯定是没问题的
个人估计,导致 《 》两种不同的结果可能是这样的原因,<<其实相当于原来的数值翻一倍速,而>>等于数值除以二,编译器认为,在除法进行之前取反,进行除法后再取反,结果是等同于直接进行除法,所以两个~被优化掉了,不过实在是很怪异,怎么也搞不行 我 1L 犯错误了。
原因应该是这样的:<< >> 运算应该是带符号的,而不是逻辑左右移,睡觉,晚上再验证。 编写了个程序,验证了一下。
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,则结果等于左操作数。
-------------------------------
所以,还是改改程序吧,这个平时没注意,又学会一点。 我试着根据你的资料归纳下
有两点与这程序相关的:
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
得出的结果是我想要的,所以这个假设应该行得通 感谢楼上的兄台,看来得自己多查查资料才是,avrfreaks上面有人就问同样的问题,看到另一种解决方法是
PORTB = ~(((unsigned char)(~PORTB))>>1),把左操作数的类型再强制转换过来就行
页:
[1]