2013-05-27 136 views
4

好了,一切從這裏開始: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) 

問題是:爲什麼會發生這種情況?爲什麼GCC「跳過」第一個IF,我如何強制GCC不跳過它?優化被禁用,但它似乎仍然優化了一些東西...

+0

由於1.O0標誌實際上並不關閉所有優化2.GCC的優化器比MSVC的優化器更智能。 – 2013-05-27 12:02:33

+0

@ H2CO3那麼我該如何關閉所有的優化?即使是來自靜態分析的那些? :) – fritzone

+0

如果你使用-O0並且如果你不在命令行中設置-O,你會得到相同的結果嗎? –

回答

7

我的猜測(我不是GCC開發者)是它做了足夠的靜態分析來證明自己第一個if的測試是從來沒有真正。

這不應該是很難,因爲在初始化和測試之間沒有代碼,所以任何副作用或外部實體都無法更改變量。

只是爲了好奇,請嘗試使變量static和/或volatile看看是否有任何變化。

+1

標記它不穩定應該工作。如果沒有,則聲明一個int,然後聲明一個int指針,然後通過volatile指針更新該值。 – slugonamission

+1

指定靜態或易失性實際上使「缺少的代碼」出現:)好抓! – fritzone

1

更新:我的猜測是,作爲低於base(int)類型的類型,char只是放大到整數類型進行比較。 (假設編譯器把文字爲整數,一般喜歡在字節字大小的整數大小的)

而作爲一個無符號數,零擴展始終爲正(注意MOVZX,而不是簽署變種!),所以檢查通過基本的常量傳播可能被優化掉了。

你可以嘗試強制文字是一個字節(轉換或後綴),例如,與((unsigned char)( - 1))比較,然後編譯器會插入一個1字節的比較結果,結果可能會不同。

2

它看起來像GCC的問題,雖然承認是非常小的一個。

GCC's documentation website(重點煤礦):

沒有任何的優化選項,編譯器的目標是降低編譯成本,使調試產生預期的效果。 語句是獨立的:如果用語句間斷點停止程序,則可以爲任何變量賦值一個新值,或將程序計數器更改爲函數中的任何其他語句,並獲得您期望從源代碼得到的結果。因此,與-O0

,你應該能夠把一個斷點unsigned char k=-1;if(k==-1)之間,該斷點期間修改k,並期望將要採取的分支;但是這對於發射的代碼來說是不可能的。

+0

OP指定了「-O0」,其中*是一個優化選項,不是? –

+0

@OwenWengerd起初我曾考慮過,並試圖對文檔進行更好的理解。我認爲關鍵是'-O0'是默認設置,如果沒有其他選項被使用,這就是適用的 - 所以沒有辦法不提供任何優化選項,我認爲這是文檔意思是「沒有任何優化選項」。 – Oak

0

有一些細微的點這裏:

  • 編譯器甚至沒有看k的初始化,以證明該條件K == - 1永遠不能無符號的字符是真實的案件。重點是,無符號的 8位值需要提升到32位,因爲比較的右邊是一個整數常量,默認是32位。由於k是無符號的,因此此促銷的結果將爲00000000 00000000 00000000 xxxxxxxx。常數-1具有位模式11111111 11111111 11111111 11111111,所以xxxxxxxx是什麼並不重要,比較的結果將始終爲假。
  • 我可能在這一點上是錯誤的,但我相信,即使k被指定爲volatile,編譯器只需要將其加載到一個寄存器中(因爲加載操作可能會在硬件中引發一些所需的副作用) ,而不是實際執行比較或爲無法訪問的if塊生成代碼。
  • 實際上,忽略生成不可達代碼的程序集完全符合-O0的目標來加快編譯過程。 AFAIK,無符號和負常數之間的比較無論如何都是未定義的行爲。至少,沒有任何機器指令可以正確處理這種情況,並且編譯器不會像插入反彙編時所看到的那樣插入必要的代碼來處理它。所有你得到的是一個隱含的轉換之間的簽名和無符號導致整數溢出(這是本身未定義的行爲),和混合符號的比較。