Visual c++ (VC+;+;)未初始化变量的运行时检查:测试是如何实现的?

Visual c++ (VC+;+;)未初始化变量的运行时检查:测试是如何实现的?,visual-c++,assembly,compiler-construction,x86,Visual C++,Assembly,Compiler Construction,X86,我想知道这个测试到底做了什么。这个玩具代码 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推式edi

我想知道这个测试到底做了什么。这个玩具代码

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推式edi
012C2DFC lea edi,[ebp-0D8h]
012C2E02 mov ecx,36小时
012C2E07 mov eax,0CCCCCH
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+3Bh(012C2E2Bh)
012C2E1E推送12C2E5Ch
012C2E23呼叫\uuuuRTC\uuUninituse(012C10B9h)

012C2E28添加esp,4
012C2E2B电影,esp
012C2E2D mov eax,dword ptr[i]
012C2E30推送eax
012C2E31推动12C5858h
012C2E36呼叫dword ptr ds:[12C9114h]
012C2E3C添加esp,8
012C2E3F cmp esi,esp
012C2E41呼叫\uuuRTC\uCHECKESP(012C1140h)

012C2E46异或eax,eax
} 012C2E48 pop edi
012C2E49 pop esi
012C2E4A流行ebx
012C2E4B添加esp,0D8h
012C2E51凸轮轴位置ebp,esp
012C2E53呼叫\uuuRTC\uCHECKESP(012C1140h)
012C2E58 mov esp,ebp
012C2E5A流行ebp
012C2E5B ret

强调的5行是通过正确初始化变量i删除的唯一行。行“push 12C2E5Ch,call _RTC_UninitUse”调用显示错误框的函数,指针指向包含变量名(“i”)的字符串作为参数

我无法理解的是执行实际测试的3条线路:

012C2E0E mov字节ptr[ebp-0D1h],0
012C2E15 cmp字节ptr[ebp-0D1h],0
012C2E1C jne wmain+3Bh(012C2E2Bh)

编译器似乎在探测i的堆栈区域(将一个字节设置为零并立即测试它是否为零),只是为了确保它没有在构建过程中看不到的地方初始化。然而,探测地址ebp-0D1h与i的实际地址关系不大

更糟糕的是,如果存在这样一个外部(其他线程?)初始化,它确实初始化了被探测的地址,但将其设置为零,那么这个测试仍然会对未初始化的变量大喊大叫


发生什么事了?也许探测的目的完全不同,比如测试某个字节是否可写?

我猜:编译器可能会在内存中分配标志,显示变量的初始化状态。对于变量
i
,这是
[ebp-0D1h]
处的一个字节。此字节的归零表示
i
未初始化。我假设如果你初始化
I
这个字节将被设置为非零。尝试这样的运行时操作:
if(argc>1)i=1这应该生成代码,而不是忽略整个检查。您还可以添加另一个变量,看看是否有两个不同的标志


在这种情况下,标志的归零和测试恰好是连续的,但情况可能并非总是如此。

[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;
}
生成以下内容(跳过不相关的部分):

在prolog中,变量用0xCC填充,两个跟踪变量(一个用于
i
,一个用于
j
)设置为0

; 7    :        printf("%d %d", i, j);    
    cmp BYTE PTR $T4693[ebp], 0
    jne SHORT $LN3@main
    push    OFFSET $LN4@main
    call    __RTC_UninitUse
    add esp, 4
$LN3@main:
    cmp BYTE PTR $T4694[ebp], 0
    jne SHORT $LN5@main
    push    OFFSET $LN6@main
    call    __RTC_UninitUse
    add esp, 4
$LN5@main:
    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
,跟踪变量设置为1

; 10   :        j = 2;
mov BYTE PTR $T4693[ebp], 1
mov DWORD PTR _j$[ebp], 2
在这里,
j
也是如此

C7060F000055    mov     dword ptr [esi],5500000Fh
C746048BEC5151  mov     dword ptr [esi+0004],5151EC8Bh
b。以及它的后代之一:

BF0F000055  mov     edi,5500000Fh
893E    mov     [esi],edi
5F  pop     edi
52  push    edx
B640    mov     dh,40
BA8BEC5151  mov     edx,5151EC8Bh
53  push    ebx
8BDA    mov     ebx,edx
895E04  mov     [esi+0004],ebx
c。还有另一代重新计算(“加密”)的“常量”数据:


代码本身正在将它测试的位置归零,因此永远不应该执行分支,从而打印消息。这对我来说没有多大意义,除非这是在没有优化的情况下编译的。/RTC只存在于未优化的版本中。是的!我以前尝试过用不同的代码来验证这一点,但没有成功。甚至添加'void dummy(const int&arg){printf(“42”);}'并调用'dummy(i);'删除了测试代码。但是,正如您所建议的那样,在argc上进行分支确实会将标志的存储与其测试分开。谢谢这个答案的意义是什么?该代码没有出现在问题中。这似乎是荒谬的,例如,在用mov立即数覆盖整个EDX之前,
mov dh,40
。而
pop
/
push
将堆栈上的底部DWORD替换为一些以前未接触过的寄存器,但您为什么要这样做呢?这显然不像是对MSVC编译器生成的调试模式asm如何工作的问题的回答。
C7060F000055    mov     dword ptr [esi],5500000Fh
C746048BEC5151  mov     dword ptr [esi+0004],5151EC8Bh
BF0F000055  mov     edi,5500000Fh
893E    mov     [esi],edi
5F  pop     edi
52  push    edx
B640    mov     dh,40
BA8BEC5151  mov     edx,5151EC8Bh
53  push    ebx
8BDA    mov     ebx,edx
895E04  mov     [esi+0004],ebx
BB0F000055  mov     ebx,5500000Fh
891E    mov     [esi],ebx
5B  pop     ebx
51  push    ecx
B9CB00C05F  mov     ecx,5FC000CBh
81C1C0EB91F1    add     ecx,F191EBC0h ; ecx=5151EC8Bh