Почему в режиме Release часть переменных можно наблюдать в отладчике?

Рассмотрим следующий код

#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 64bit как в Release, так и в Debug (конфигурация по умолчанию).

Когда я запускаю отладчик в режиме отладки, я могу посмотреть sBasicString а также buffer переменные, как ожидается (запрос их значения и т. д.)

введите описание изображения здесь

Но когда я запускаю отладчик в режиме Release, я все еще могу смотреть sBasicString но не buffer,

введите описание изображения здесь

Зачем?

Поскольку в режиме Release для оптимизации задано значение «Полная оптимизация» (значение по умолчанию), а для параметра «Создать информацию отладки» задано значение «ДА» — можно ожидать, что обе переменные могут быть просмотрены, либо их нет.

РЕДАКТИРОВАТЬ

пытаясь добавить правильное использование buffer переменная (избегайте оптимизации компилятора) — я все еще получаю такое же поведение.

введите описание изображения здесь

РЕДАКТИРОВАТЬ 2 Добавление 64-битного дизассемблированного вывода компиляции в режиме Release

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

РЕДАКТИРОВАТЬ 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

2

Решение

Глядя на сборку x86, опубликованную в этом вопросе, я могу применить свои элементарные знания сборки, чтобы понять, где 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 Регистр процессора. Вы можете добавить строку delete[] buffer; в программу и посмотрим, как operator delete применяется к esi в сборе.

Поскольку весь цикл не содержит вызовов функций, которые могут изменять регистры ЦП, оптимизированный код, созданный компилятором, просто сохраняет буфер в регистре. Отладчик этого не знает и не может отобразить.

Сборка x64 работает таким же образом, но она сложнее и требует больше времени для понимания. Я надеюсь, у вас есть идея сейчас, что происходит.

2

Другие решения

Компилятор достаточно умен, чтобы видеть, что вы ничего не делаете с буфером, поэтому он просто оптимизировал его в режиме Release.

С другой стороны, std :: string происходит из библиотеки, и сложнее обнаружить, что присвоение или чтение из него не имеет побочных эффектов. Вот почему компилятор не удалил его.

2

По вопросам рекламы [email protected]