繁体   English   中英

带花括号的c ++堆栈内存范围

[英]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是未定义的行为 - 因此很危险 - 因为bufcharPtr大括号处超出范围。

在实践中,代码可能有效(或看起来有效),因为用于buf的内存不会立即重用,但您当然不应该依赖它。 写这段代码的人犯了一个错误。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM