momo zone

调核人的blog

Monthly Archives: 八月 2016

一道C题

#include<sdtio.h>  

int main(void)
{
    unsigned char a = 0xa5;
    printf("%d\n",~a);
    char b = ~a;
    printf("%d\n",b);
    unsigned char c = ~a;
    printf("%d\n",c);

    return 0;
}  



看起来很简单,大多数人的答案是90,90,90。但正确答案是-166,90,90 出乎意料,第一个竟然是负数。关键在于那个取反 ~a,先反汇编看一下:

    unsigned char a = 0xa5;
 8048446:       c6 44 24 1f a5          movb   $0xa5,0x1f(%esp)
    printf("%d\n",~a);
 804844b:       0f b6 44 24 1f          movzbl 0x1f(%esp),%eax
 8048450:       f7 d0                   not    %eax
 8048452:       89 44 24 04             mov    %eax,0x4(%esp)
 8048456:       c7 04 24 40 85 04 08    movl   $0x8048540,(%esp)
 804845d:       e8 ae fe ff ff          call   8048310 <printf@plt>
    char b = ~a;
 8048462:       0f b6 44 24 1f          movzbl 0x1f(%esp),%eax
 8048467:       f7 d0                   not    %eax
 8048469:       88 44 24 1e             mov    %al,0x1e(%esp)
    printf("%d\n",b);
 804846d:       0f be 44 24 1e          movsbl 0x1e(%esp),%eax
 8048472:       89 44 24 04             mov    %eax,0x4(%esp)
 8048476:       c7 04 24 40 85 04 08    movl   $0x8048540,(%esp)
 804847d:       e8 8e fe ff ff          call   8048310 <printf@plt>
    unsigned char c = ~a;
 8048482:       0f b6 44 24 1f          movzbl 0x1f(%esp),%eax
 8048487:       f7 d0                   not    %eax
 8048489:       88 44 24 1d             mov    %al,0x1d(%esp)
    printf("%d\n",c);
 804848d:       0f b6 44 24 1d          movzbl 0x1d(%esp),%eax
 8048492:       89 44 24 04             mov    %eax,0x4(%esp)
 8048496:       c7 04 24 40 85 04 08    movl   $0x8048540,(%esp)
 804849d:       e8 6e fe ff ff          call   8048310 <printf@plt>

取反指令是NOT ,对于32位平台,NOT后面可以操作8位,16位,32位寄存器或地址,但gcc编译出来的代码都是操作32位的,而不管栈上面的那个变量类型。也就说凡是对短于32的变量取反,内存里面的最高为肯定是1,格式化输出有符号数的结果肯定是负数。上例中的也就是0x5a => 0xffffffa5,再套上printf的转换反码加1 为-0x5b=-166

对于64位平台(也就是跑64模式的cpu),NOT后面可以操作8位,16位,32位,64位寄存器或地址,但不同的是8位里面AH,BH,CH,DH寄存器不能访问。而且同32位平台一样,gcc编译出来的还是访问32位的寄存器或地址。所以结果还是-166

权威解释可以看intel的指令手册3B

为什么gcc会这样生成代码?我瞎猜一下gcc为了偷懒把x86的短于64位的取反做一个通用处理,一律用32位来搞?

Advertisements