2014-12-02 74 views
2

我試圖瞭解這個測試完全是什麼。這種玩具碼(VC++)運行時檢查未初始化的變量:測試如何實現?

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    int i; 
    printf("%d", i); 
    return 0; 
} 

編譯成這樣:

int _tmain(int argc, _TCHAR* argv[]) 

{ 012C2DF0推EBP
012C2DF1 MOV EBP,ESP
012C2DF3子ESP,0D8h
012C2DF9推EBX
012C2DFA推ESI
012C2DFB push edi
012C2DFC lea EDI,[EBP-0D8h]
012C2E02 MOV ECX,36H
012C2E07 MOV EAX,0CCCCCCCCh
012C2E0C代表STOS DWORD PTR ES:[EDI]
012C2E0E MOV字節的ptr [EBP-0D1h],0

int i; 
printf("%d", i); 

012C2E15 CMP字節的ptr [EBP-0D1h],0
012C2E1C JNE wmain + 3 BH(012C2E2Bh)
012C2E1E推12C2E5Ch
012C2E23呼叫__RTC_ UninitUse(012C10B9h)

012C2E28添加ESP,4
012C2E2B MOV ESI,尤指
012C2E2D MOV EAX,DWORD PTR [I]
012C2E30推EAX
012C2E31推12C5858h
012C2E36呼叫DWORD PTR DS:[12C9114h ]
012C2E3C添加ESP,8
012C2E3F CMP ESI,尤指
012C2E41呼叫__RTC_CheckEsp(012C1140h)

return 0; 

012C2E46 XOR EAX,EAX
} 012C2E48彈出EDI
012C2E49彈出ESI
012C2E4A彈出EBX
012C2E4B ADD ESP,0D8h
012C2E51 CMP EBP,ESP
012C2E53呼叫__RTC_CheckEsp(012C1140h)
012C2E58 MOV ESP,EBP
012C2E5A彈出EBP
012C2E5B RET

的5條線EM phasized是唯一通過正確初始化變量i而被刪除的。行'push 12C2E5Ch,調用__RTC_UninitUse'調用顯示錯誤框的函數,指向包含變量名稱(「i」)的字符串作爲參數。

我無法理解是第3行執行實際測試:

012C2E0E MOV字節的ptr [EBP-0D1h],0
012C2E15 CMP字節的ptr [EBP-0D1h],0
012C2E1C jne wmain + 3Bh(012C2E2Bh)

看起來編譯器正在探測i的堆棧區域(將字節設置爲零並立即測試它是否爲零),只是爲了確保它未在某處被初始化在構建過程中無法看到。但是,探測到的地址ebp-0D1h與我的實際地址無關。

更糟的是,它似乎如果有這樣一個外部人(其他線程?)初始化這並初始化探測的地址,但,本次測試仍然會喊的變量未初始化。

發生了什麼事?也許這個探測針對的是完全不同的東西,比如測試某個字節是否可寫?

+0

該代碼本身正在調零它測試的位置,所以不應該採取該分支並因此打印該消息。這對我來說沒有多大意義,除非這是在沒有優化的情況下編譯的。 – Jester 2014-12-02 14:42:19

+0

/RTC只能在未經優化的版本中生存。 – 2014-12-02 14:43:35

回答

2

這是我的猜測:編譯器可能會在內存中分配顯示變量初始化狀態的標誌。在你的情況下,變量爲i這是一個單字節在[ebp-0D1h]。該字節的調零意味着i未被初始化。我假設如果你初始化i這個字節將被設置爲非零。試試這樣的運行時間:if (argc > 1) i = 1;這應該生成代碼而不是省略整個檢查。你也可以添加另一個變量,看看你是否有兩個不同的標誌。

在這種情況下,標誌的置零和測試恰好是連續的,但事實並非總是如此。

+0

是的!我試圖用不同的代碼來驗證它,但不能。甚至加入'void dummy(const int&arg){printf(「42」); }'並調用'dummy(i);'刪除了測試代碼。但是按照你的建議在argc上分支的時候,會將旗子的存儲與測試分開。謝謝! – 2014-12-02 15:03:27

5

[ebp-0D1h]是編譯器用來跟蹤變量的「初始化」狀態的臨時變量。如果我們修改源一點,會更清楚:

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    int i, j; 
    printf("%d %d", i, j); 
    i = 1; 
    printf("%d %d", i, j); 
    j = 2; 
    return 0; 
} 

產生如下(不相關的部分跳過):

mov DWORD PTR [ebp-12], -858993460  ; ccccccccH 
mov DWORD PTR [ebp-8], -858993460  ; ccccccccH 
mov DWORD PTR [ebp-4], -858993460  ; ccccccccH 
mov BYTE PTR $T4694[ebp], 0 
mov BYTE PTR $T4693[ebp], 0 

在序言中,變量充斥着的0xCC和兩個跟蹤變量(一個用於i,一個用於j)被設置爲0

; 7 :  printf("%d %d", i, j);  
    cmp BYTE PTR $T4693[ebp], 0 
    jne SHORT [email protected] 
    push OFFSET [email protected] 
    call __RTC_UninitUse 
    add esp, 4 
[email protected]: 
    cmp BYTE PTR $T4694[ebp], 0 
    jne SHORT [email protected] 
    push OFFSET [email protected] 
    call __RTC_UninitUse 
    add esp, 4 
[email protected]: 
    mov eax, DWORD PTR _j$[ebp] 
    push eax 
    mov ecx, DWORD PTR _i$[ebp] 
    push ecx 
    push OFFSET $SG4678 
    call _printf 
    add esp, 12     ; 0000000cH 

這大致對應於:

if ($T4693 == 0) 
    _RTC_UninitUse("j"); 
if ($T4694 == 0) 
    _RTC_UninitUse("j"); 
printf("%d %d", i, j); 

下一個部分:

; 8 :  i = 1;  
    mov BYTE PTR $T4694[ebp], 1 
    mov DWORD PTR _i$[ebp], 1 

因此,一旦i是intialized,跟蹤變量設置爲1。

; 10 :  j = 2; 
mov BYTE PTR $T4693[ebp], 1 
mov DWORD PTR _j$[ebp], 2 

這裏,同樣是發生了j