Consider the following code..
#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;
}
(Please ignore the memory leak - it is not relevant)
I'm compiling it in VS2012 64bit in both Release and Debug (default configuration).
When i'm running the debugger in Debug mode, i can watch the sBasicString
and buffer
variables as expected (query their value, etc...)
But when i'm running the debugger in Release mode, i still can watch the sBasicString
but not the buffer
.
Why?
As Release mode has optimization set to "Full Optimization" (default value) and "Generate Debug Info" set to "YES" - i would expect either both variables can be watched or none.
EDIT
trying to add a proper usage of the buffer
variable (avoid compiler optimization) - i still get the same behavior.
EDIT 2 Adding 64bit disassemble output of Release mode compilation
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 Adding 32bit disassemble output
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
Looking at x86 Assembly posted in this question, I can apply my rudimentary Assembly knowledge to understand, where the buffer
variable is hidden:
char* buffer = new char[1000];
013B105B call operator new[] (013B1BD6h)
013B1065 mov esi,eax
My candidate is esi
register: operator new
returned the result in eax
and it is moved to esi
. Let's follow this register:
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
The last line places char value al
to the buffer
. edx
is obviously the loop counter, see ind edx
. So, esi
points to the buffer allocated by operator new
. And finally:
013B1093 push esi
013B1094 push dword ptr ds:[13B3034h]
013B109A call std::operator<<<std::char_traits<char> > (013B10F0h)
Here esi
is printed. So, the answer to your question: buffer
variable is kept in the esi
CPU register. You can add the line delete[] buffer;
to the program and see, how operator delete
is applied to esi
in Assembly.
Since the whole loop doesn't contain function calls that can change CPU registers, optimized code produced by compiler just keeps the buffer in the register. Debugger doesn't know this and cannot display it.
x64 Assembly works by the same way, but it is more complicated and requires more time to understand. I hope you have an idea now, what happens.