编译器代码生成比较

时间:2013-05-27 12:00:15

标签: c visual-c++ gcc assembly compiler-construction

好的,所以一切都从这里开始:Unsigned integer and unsigned char holding same value yet behaving differently why?

我编写了以下应用程序来了解幕后发生的事情(即编译器如何处理这个问题)。

#include <stdio.h>

int main()
{
  {
  unsigned char k=-1;
  if(k==-1)
  {
    puts("uc ok\n");
  }
  }

  {
  unsigned int k=-1;
  if(k==-1)
  {
    puts("ui ok");
  }
  }
}

在用GCC编译时,如:

gcc -O0 -S -masm=intel h.c 

我得到以下程序集文件:

    .file   "h.c"
    .intel_syntax noprefix
    .section        .rodata
.LC0:
    .string "ui ok"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    push    rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    mov     rbp, rsp
    .cfi_def_cfa_register 6
    sub     rsp, 16
    mov     BYTE PTR [rbp-1], -1
    mov     DWORD PTR [rbp-8], -1
    cmp     DWORD PTR [rbp-8], -1
    jne     .L3
    mov     edi, OFFSET FLAT:.LC0
    call    puts
.L3:
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
    .section        .note.GNU-stack,"",@progbits

令我惊讶的是,第一次检查并非偶然。

但是,如果我用Microsoft Visual C ++(2010)编译相同的东西,我得到(我已经从这个列表中切掉了很多垃圾,这就是为什么它不那么有效):

00B81780  push        ebp  
00B81781  mov         ebp,esp  
00B81783  sub         esp,0D8h  
00B81789  push        ebx  
00B8178A  push        esi  
00B8178B  push        edi  
00B8178C  lea         edi,[ebp-0D8h]  
00B81792  mov         ecx,36h  
00B81797  mov         eax,0CCCCCCCCh  
00B8179C  rep stos    dword ptr es:[edi]  
00B8179E  mov         byte ptr [k],0FFh  
00B817A2  movzx       eax,byte ptr [k]  
00B817A6  cmp         eax,0FFFFFFFFh  
00B817A9  jne         wmain+42h (0B817C2h)  
00B817AB  mov         esi,esp  
00B817AD  push        offset string "uc ok\n" (0B857A8h)  
00B817B2  call        dword ptr [__imp__puts (0B882ACh)]  
00B817B8  add         esp,4  
00B817BB  cmp         esi,esp  
00B817BD  call        @ILT+435(__RTC_CheckEsp) (0B811B8h)  
00B817C2  mov         dword ptr [k],0FFFFFFFFh  
00B817C9  cmp         dword ptr [k],0FFFFFFFFh  
00B817CD  jne         wmain+66h (0B817E6h)  
00B817CF  mov         esi,esp  
00B817D1  push        offset string "ui ok" (0B857A0h)  
00B817D6  call        dword ptr [__imp__puts (0B882ACh)]  
00B817DC  add         esp,4  
00B817DF  cmp         esi,esp  
00B817E1  call        @ILT+435(__RTC_CheckEsp) (0B811B8h)  

问题是:为什么会这样?为什么海湾合作委员会“跳过”第一个IF,我怎么能强迫GCC不跳过它?优化被禁用,但它似乎仍然优化了一些东西......

4 个答案:

答案 0 :(得分:7)

我的猜测(我不是GCC开发人员)是它做了足够的静态分析来向自己证明第一个if的测试永远不会成真。

这不应该很难,因为初始化和测试之间没有代码,任何副作用或外部实体都无法改变变量。

只是出于好奇,尝试制作变量static和/或volatile以查看是否有任何变化。

答案 1 :(得分:2)

这看起来像海湾合作委员会的一个问题,虽然这是一个非常小的问题。

来自GCC's documentation website(强调我的):

  

如果没有任何优化选项,编译器的目标是降低编译成本并使调试产生预期结果。 语句是独立的:如果在语句之间使用断点停止程序,则可以为任何变量分配新值或将程序计数器更改为函数中的任何其他语句并获得完全结果期待源代码。

因此,对于-O0,您应该能够在unsigned char k=-1;if(k==-1)之间放置一个断点,在该断点期间修改k,并期望采取分支;但这对于发出的代码是不可能的。

答案 2 :(得分:1)

更新:我的猜测是,char(作为base(int)类型下面的类型)只是放大到整数类型进行比较。 (假设编译器将文字作为整数,并且通常更喜欢字大小的整数而不是字节大小的整数)

作为无符号值,零扩展始终为正(请注意 MOVZX 而不是带符号的变体!),因此检查可能会被基本的常量传播优化掉。

您可以尝试强制文字为字节(强制转换或后缀),例如与((unsigned char)( - 1))比较,然后编译器可能会插入一个1字节的比较,结果可能会有所不同。

答案 3 :(得分:0)

这里有很多细节:

  • 编译器甚至不必查看k的初始化来证明条件k == - 1在unsigned char情况下永远不会为真。关键是,无符号 8位值需要提升为32位,因为比较的右侧是一个整数常量,默认为32位。由于k是未签名的,因此此促销的结果为00000000 00000000 00000000 xxxxxxxx。常量-1具有位模式11111111 11111111 11111111 11111111,因此xxxxxxxx无关紧要,比较结果将始终为假。
  • 我可能在这一点上错了,但我相信即使k被指定为volatile,编译器也只需要将其加载到寄存器中(因为加载操作可能会在硬件中触发一些所需的副作用) ,不要实际执行比较或为无法访问的if-block生成代码。
  • 实际上,省略为无法访问的代码生成程序集完全符合-O0的目标,以加快编译过程。
  • AFAIK,无符号和负常数之间的比较无论如何都是未定义的行为。至少,没有机器指令来正确处理案例,编译器不会插入必要的代码来在软件中处理它,如从反汇编中看到的那样。你得到的只是有符号和无符号之间的隐式转换,导致整数溢出(这本身就是未定义的行为),以及未混合符号的比较。
相关问题