【问题标题】:Why in Release mode part of the variables can be watched within a debugger?为什么在发布模式下可以在调试器中查看部分变量?
【发布时间】:2014-09-21 18:25:00
【问题描述】:

考虑下面的代码..

#include <vector>

std::basic_string<char> sBasicString = "basic_string";
char* buffer = new char[1000];
for (size_t i = 0 ; i < sBasicString.size() ; ++i)
{
    char c;
    c = sBasicString[i];
    buffer[i] = c;  
}

(请忽略内存泄漏 - 这无关紧要)

我在 VS2012 64 位版本和调试(默认配置)中编译它。

当我在调试模式下运行调试器时,我可以按预期观察sBasicString 和buffer 变量(查询它们的值等...)

但是当我在发布模式下运行调试器时,我仍然可以看到sBasicString,但看不到buffer。

为什么?

由于发布模式的优化设置为“完全优化”(默认值),“生成调试信息”设置为“是” - 我希望这两个变量都可以观看或不观看。

编辑

尝试添加 buffer 变量的正确用法(避免编译器优化) - 我仍然得到相同的行为。

EDIT 2添加Release模式编译的64位反汇编输出

int main()
{
000000013F091000  mov         rax,rsp  
000000013F091003  push        rbx  
000000013F091004  sub         rsp,50h  
000000013F091008  mov         qword ptr [rax-38h],0FFFFFFFFFFFFFFFEh  
    std::basic_string<char> sBasicString = "basic_string";
000000013F091010  xor         ebx,ebx  
000000013F091012  mov         qword ptr [rax-20h],rbx  
000000013F091016  mov         qword ptr [rax-18h],rbx  
000000013F09101A  mov         qword ptr [rax-18h],0Fh  
000000013F091022  mov         qword ptr [rax-20h],rbx  
000000013F091026  mov         byte ptr [rax-30h],bl  
000000013F091029  lea         r8d,[rbx+0Ch]  
000000013F09102D  lea         rdx,[__xi_z+40h (013F093238h)]  
000000013F091034  lea         rcx,[rax-30h]  
000000013F091038  call        std::basic_string<char,std::char_traits<char>,std::allocator<char> >::assign (013F0916A0h)  
000000013F09103D  nop  
    char* buffer = new char[1000];
000000013F09103E  mov         ecx,3E8h  
000000013F091043  call        operator new[] (013F091AD8h)  
    for (size_t i = 0 ; i < sBasicString.size() ; ++i)
000000013F091048  mov         edx,ebx  
000000013F09104A  cmp         qword ptr [rsp+38h],rbx  
000000013F09104F  jbe         main+73h (013F091073h)  
    {
        char c;
        c = sBasicString[i];
000000013F091051  lea         rcx,[sBasicString]  
000000013F091056  cmp         qword ptr [rsp+40h],10h  
000000013F09105C  cmovae      rcx,qword ptr [sBasicString]  
        buffer[i] = c;  
000000013F091062  movzx       ecx,byte ptr [rcx+rdx]  
        buffer[i] = c;  
000000013F091066  mov         byte ptr [rdx+rax],cl  
    for (size_t i = 0 ; i < sBasicString.size() ; ++i)
000000013F091069  inc         rdx  
000000013F09106C  cmp         rdx,qword ptr [rsp+38h]  
000000013F091071  jb          main+51h (013F091051h)  
    }

    std::cout << buffer << std::endl;
000000013F091073  mov         rdx,rax  
000000013F091076  mov         rcx,qword ptr [__imp_std::cout (013F093068h)]  
000000013F09107D  call        std::operator<<<std::char_traits<char> > (013F0910C0h)  
000000013F091082  mov         rcx,rax  
000000013F091085  mov         rdx,qword ptr [__imp_std::endl (013F093060h)]  
000000013F09108C  call        qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (013F093098h)]  
000000013F091092  nop  

    return 0;
000000013F091093  cmp         qword ptr [rsp+40h],10h  
000000013F091099  jb          main+0A5h (013F0910A5h)  
000000013F09109B  mov         rcx,qword ptr [sBasicString]  
000000013F0910A0  call        operator delete (013F091AEAh)  
000000013F0910A5  mov         qword ptr [rsp+40h],0Fh  
000000013F0910AE  mov         qword ptr [rsp+38h],rbx  
000000013F0910B3  mov         byte ptr [sBasicString],0  

    return 0;
000000013F0910B8  xor         eax,eax  
}
000000013F0910BA  add         rsp,50h  
000000013F0910BE  pop         rbx  
000000013F0910BF  ret  

EDIT 3添加32位反汇编输出

int main()
{
013B1000  push        0FFFFFFFFh  
013B1002  push        13B2558h  
013B1007  mov         eax,dword ptr fs:[00000000h]  
013B100D  push        eax  
013B100E  mov         dword ptr fs:[0],esp  
013B1015  sub         esp,18h  
013B1018  push        esi  
    std::basic_string<char> sBasicString = "basic_string";
013B1019  push        0Ch  
013B101B  mov         dword ptr [esp+18h],0  
013B1023  mov         dword ptr [esp+1Ch],0  
013B102B  push        13B3158h  
013B1030  lea         ecx,[esp+0Ch]  
013B1034  mov         dword ptr [esp+20h],0Fh  
013B103C  mov         dword ptr [esp+1Ch],0  
013B1044  mov         byte ptr [esp+0Ch],0  
013B1049  call        std::basic_string<char,std::char_traits<char>,std::allocator<char> >::assign (013B17B0h)  
013B104E  mov         dword ptr [esp+24h],0  
    char* buffer = new char[1000];
013B1056  push        3E8h  
013B105B  call        operator new[] (013B1BD6h)  
    for (size_t i = 0 ; i < sBasicString.size() ; ++i)
013B1060  xor         edx,edx  
013B1062  add         esp,4  
013B1065  mov         esi,eax  
013B1067  cmp         dword ptr [esp+14h],edx  
013B106B  jbe         main+8Dh (013B108Dh)  
    for (size_t i = 0 ; i < sBasicString.size() ; ++i)
013B106D  lea         ecx,[ecx]  
    {
        char c;
        c = sBasicString[i];
013B1070  cmp         dword ptr [esp+18h],10h  
013B1075  lea         ecx,[esp+4]  
013B1079  cmovae      ecx,dword ptr [esp+4]  
    for (size_t i = 0 ; i < sBasicString.size() ; ++i)
013B107E  inc         edx  
        buffer[i] = c;  
013B107F  mov         al,byte ptr [ecx+edx-1]  
013B1083  mov         byte ptr [edx+esi-1],al  
013B1087  cmp         edx,dword ptr [esp+14h]  
013B108B  jb          main+70h (013B1070h)  
    }

    std::cout << buffer << std::endl;
013B108D  push        dword ptr ds:[13B3030h]  
013B1093  push        esi  
013B1094  push        dword ptr ds:[13B3034h]  
013B109A  call        std::operator<<<std::char_traits<char> > (013B10F0h)  
013B109F  add         esp,8  
013B10A2  mov         ecx,eax  
013B10A4  call        dword ptr ds:[13B3028h]  

    return 0;
013B10AA  mov         dword ptr [esp+24h],0FFFFFFFFh  
013B10B2  cmp         dword ptr [esp+18h],10h  
013B10B7  pop         esi  
013B10B8  jb          main+0C5h (013B10C5h)  
013B10BA  push        dword ptr [esp]  
013B10BD  call        operator delete (013B1BECh)  
013B10C2  add         esp,4  
}
013B10C5  mov         ecx,dword ptr [esp+18h]  

    return 0;
013B10C9  mov         dword ptr [esp+14h],0Fh  
013B10D1  mov         dword ptr [esp+10h],0  
013B10D9  mov         byte ptr [esp],0  
013B10DD  xor         eax,eax  
}
013B10DF  mov         dword ptr fs:[0],ecx  
013B10E6  add         esp,24h  
013B10E9  ret  

【问题讨论】:

  • 打开反汇编窗口以了解发生了什么。优化后的代码可能会将这个变量保留在 CPU 寄存器中,如果没有副作用,甚至可以删除整个代码片段。
  • 可能只有这段代码的第一行保留在优化代码中。编译器不会删除类实例创建,因为它可能有副作用(例如火箭导弹)。尝试打印buffer 变量,看看会发生什么。
  • @AlexFarber - 检查我的编辑.. 我已经添加了 buffer 变量的正确用法 - 仍然是同样的问题......
  • 它可能在 CPU 寄存器中。理解这一点的唯一方法是查看汇编代码。您可以将汇编代码添加到问题中。更好的文字而不是截图,我们是程序员......
  • @AlexFarber - 已添加(抱歉,这篇文章太长了...)

标签: c++ debugging visual-studio-2012 stl internals


【解决方案1】:

看看这个问题中发布的x86 Assembly,我可以应用我的基本Assembly知识来了解buffer变量隐藏在哪里:

char* buffer = new char[1000];
013B105B  call        operator new[] (013B1BD6h) 
013B1065  mov         esi,eax

我的候选人是esi 注册:operator new 在eax 中返回结果,它被移动到esi。让我们关注这个寄存器:

    for (size_t i = 0 ; i < sBasicString.size() ; ++i)
013B107E  inc         edx  
    buffer[i] = c;  
013B107F  mov         al,byte ptr [ecx+edx-1]  
013B1083  mov         byte ptr [edx+esi-1],al 

最后一行将 char 值 al 放入 buffer。 edx 显然是循环计数器,请参阅 ind edx。所以,esi 指向operator new 分配的缓冲区。最后:

013B1093  push        esi  
013B1094  push        dword ptr ds:[13B3034h]  
013B109A  call        std::operator<<<std::char_traits<char> > (013B10F0h)

这里打印了esi。所以,你的问题的答案:buffer 变量保存在esi CPU 寄存器中。您可以将delete[] buffer; 行添加到程序中,看看operator delete 如何在Assembly 中应用于esi。

由于整个循环不包含可以更改 CPU 寄存器的函数调用,编译器生成的优化代码只是将缓冲区保留在寄存器中。调试器不知道这一点,无法显示。

x64 汇编的工作方式相同,但它更复杂,需要更多时间来理解。我希望你现在有一个想法,会发生什么。

【讨论】:

    【解决方案2】:

    编译器足够聪明,可以看到您没有对缓冲区做任何事情,因此它只是在发布模式下对其进行了优化。

    另一方面,

    std::string 来自一个库,并且很难检测到分配给它或从中读取它没有副作用。这就是编译器没有删除它的原因。

    【讨论】:

    • 您稍后可以看到buffer 变量的用法 - 它仍然被编译器删除?
    • 通过反汇编模式,看看会发生什么——我敢打赌,它会保存在一个寄存器中。
    • 看看,我添加了32位和64位反汇编输出。
    • 是的,“缓冲区”在这两种情况下都加载到了 ecx/rcx 寄存器中。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-11
    • 1970-01-01
    • 1970-01-01
    • 2017-07-29
    相关资源
    最近更新 更多