[英]c++ stack memory scope with curly braces
我正在寻找对我的理解的双重检查。 我遇到了这种形式的代码:
#define BUFLEN_256 256
int main()
{
const char* charPtr = "";
if (true /* some real test here */)
{
char buf[BUFLEN_256] = { 0 };
snprintf(buf, BUFLEN_256, "Some string goes here..");
charPtr = buf;
}
std::cout << charPtr << std::endl; // Is accessing charPtr technically dangerous here?
}
我的直接想法是错误,一旦退出 if(){},分配给 buf[] 的堆栈内存不再保证属于该数组。 但是代码构建和运行没有问题,在仔细检查自己时我感到困惑。 我不擅长汇编,但是如果我正确阅读它,则在离开花括号后似乎不会重置堆栈指针。 有人可以仔细检查我并补充一下这段代码在技术上是否有效吗? 这是程序集的代码(使用 Visual Studio 2019 构建)。 我的想法是这段代码不好,但我以前在奇怪的问题上都错了。
#define BUFLEN_256 256
int main()
{
00DA25C0 push ebp
00DA25C1 mov ebp,esp
00DA25C3 sub esp,1D8h
00DA25C9 push ebx
00DA25CA push esi
00DA25CB push edi
00DA25CC lea edi,[ebp-1D8h]
00DA25D2 mov ecx,76h
00DA25D7 mov eax,0CCCCCCCCh
00DA25DC rep stos dword ptr es:[edi]
00DA25DE mov eax,dword ptr [__security_cookie (0DAC004h)]
00DA25E3 xor eax,ebp
00DA25E5 mov dword ptr [ebp-4],eax
00DA25E8 mov ecx,offset _1FACD15F_scratch@cpp (0DAF029h)
00DA25ED call @__CheckForDebuggerJustMyCode@4 (0DA138Eh)
const char* charPtr = "";
00DA25F2 mov dword ptr [charPtr],offset string "" (0DA9B30h)
if (true /* some real test here */)
00DA25F9 mov eax,1
00DA25FE test eax,eax
00DA2600 je main+7Ah (0DA263Ah)
{
char buf[BUFLEN_256] = { 0 };
00DA2602 push 100h
00DA2607 push 0
00DA2609 lea eax,[ebp-114h]
00DA260F push eax
00DA2610 call _memset (0DA1186h)
00DA2615 add esp,0Ch
snprintf(buf, BUFLEN_256, "Some string goes here..");
00DA2618 push offset string "Some string goes here.." (0DA9BB8h)
00DA261D push 100h
00DA2622 lea eax,[ebp-114h]
00DA2628 push eax
00DA2629 call _snprintf (0DA1267h)
00DA262E add esp,0Ch
charPtr = buf;
00DA2631 lea eax,[ebp-114h]
00DA2637 mov dword ptr [charPtr],eax
}
std::cout << charPtr << std::endl;
00DA263A mov esi,esp
00DA263C push offset std::endl<char,std::char_traits<char> > (0DA103Ch)
00DA2641 mov eax,dword ptr [charPtr]
00DA2644 push eax
00DA2645 mov ecx,dword ptr [__imp_std::cout (0DAD0D4h)]
00DA264B push ecx
00DA264C call std::operator<<<std::char_traits<char> > (0DA11AEh)
00DA2651 add esp,8
00DA2654 mov ecx,eax
00DA2656 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0DAD0A0h)]
00DA265C cmp esi,esp
00DA265E call __RTC_CheckEsp (0DA129Eh)
}
00DA2663 xor eax,eax
00DA2665 push edx
00DA2666 mov ecx,ebp
00DA2668 push eax
00DA2669 lea edx,ds:[0DA2694h]
00DA266F call @_RTC_CheckStackVars@8 (0DA1235h)
00DA2674 pop eax
00DA2675 pop edx
00DA2676 pop edi
00DA2677 pop esi
00DA2678 pop ebx
00DA2679 mov ecx,dword ptr [ebp-4]
00DA267C xor ecx,ebp
00DA267E call @__security_check_cookie@4 (0DA1181h)
00DA2683 add esp,1D8h
00DA2689 cmp ebp,esp
00DA268B call __RTC_CheckEsp (0DA129Eh)
00DA2690 mov esp,ebp
00DA2692 pop ebp
00DA2693 ret
00DA2694 add dword ptr [eax],eax
00DA2696 add byte ptr [eax],al
00DA2698 pushfd
00DA2699 fiadd dword ptr es:[eax]
00DA269C in al,dx
00DA269D ?? ??????
00DA269E ?? ??????
}
您看到的是“未定义”行为。 堆栈内存通常在开始时一次性分配。 因此,当变量超出堆栈的范围时,该内存可用于重用。 由于您没有在if
语句之后用任何内容覆盖堆栈,因此先前存储在那里的数据仍然完好无损。 如果您在if
语句之后向堆栈分配额外的内存/数据,您会看到非常不同的结果。
在这里看到这篇文章: 当变量超出范围时会发生什么?
编辑:为了详细说明和演示这一点,请考虑对代码进行以下修改(在 VS2019 v142 x64 上编译):
#include <iostream>
#define BUFLEN_256 256
int main()
{
char* charPtr;
char other_buf[BUFLEN_256] = { 0 };
char* charPtr2 = other_buf;
if (true /* some real test here */)
{
char buf[BUFLEN_256] = { 0 };
snprintf(buf, BUFLEN_256, "Some string goes here..");
charPtr = buf;
}
std::cout << charPtr << std::endl;
for (int n = 0; n < 3000; ++n)
{
*charPtr2 = 'a';
charPtr2++;
}
std::cout << charPtr << std::endl;
}
输出
Some string goes here..
Some string goes haaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaca
当然,请记住,每个编译器以不同的方式处理优化,这可能会也可能不会在每种情况下发生。 这就是为什么行为是“未定义的”。 这个例子更多地展示了故意溢出堆栈(缓冲区溢出),但它说明了相同的效果。 我会给出一个更直接的例子来说明可能发生这种情况的合法案例,但具有讽刺意味的是,未定义的行为很难有意重现。
我的直接想法是错误,一旦退出
if(){}
,分配给buf[]
的堆栈内存不再保证属于该数组。
那是正确的。
但是代码构建和运行没有问题
未定义的行为。 在cout << charPtr
语句中, charPtr
是指向无效内存的悬空指针。 内存是否已被物理释放无关紧要。 内存已超出范围。
我不擅长汇编,但是如果我正确阅读它,则在离开花括号后似乎不会重置堆栈指针。
那是正确的。
当函数进入时,数组的内存被预先分配在堆栈帧的顶部(作为sub esp, 1D8h
指令的一部分),然后在函数退出时在堆栈帧的清理过程中被释放(作为add esp, 1D8h
指令的一部分)。
如您所见,当输入if
时,它所做的第一件事就是调用_memset()
将[ebp-114h]
已经存在的数组清零。
但这是一个实现细节,不要依赖它。
有人可以仔细检查我并补充一下这段代码在技术上是否有效吗?
它不是。
是的,以这种方式访问charPtr
是未定义的行为 - 因此很危险 - 因为buf
在charPtr
大括号处超出范围。
在实践中,代码可能有效(或看起来有效),因为用于buf
的内存不会立即重用,但您当然不应该依赖它。 写这段代码的人犯了一个错误。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.