【问题标题】:c++ stack memory scope with curly braces带有花括号的 c++ 堆栈内存范围
【发布时间】:2021-09-22 01:16:18
【问题描述】:

我正在仔细检查我的理解。我遇到了这种形式的代码:

#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  ?? ?????? 

}

【问题讨论】:

  • 如果代码可以编译,并且在一个特定的实例中运行正常,这并不意味着代码没有错误(或 UB)。

标签: c++ scope stack


【解决方案1】:

是的,以这种方式访问​​ charPtr 是未定义的行为 - 因此很危险 - 因为 buf 超出了右括号的范围。

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

【讨论】:

    【解决方案2】:

    您看到的是“未定义”行为。堆栈内存通常在一开始就一次性分配。因此,当变量超出堆栈范围时,该内存将可供重用。由于您没有在if 语句之后用任何内容覆盖堆栈,因此之前存储在那里的数据仍然完好无损。如果您要在 if 语句之后向堆栈分配额外的内存/数据,您会看到截然不同的结果。

    在此处查看此帖子: What happens when a variable goes out of scope?

    编辑: 为了详细说明和演示这一点,请考虑对您的代码进行以下修改(在 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
    

    当然,请记住,每个编译器处理优化的方式都不同,这可能会或可能不会在每种情况下发生。这就是行为“未定义”的原因。这个例子更多地展示了故意溢出堆栈(缓冲区溢出),但它说明了相同的效果。我会提供一个更直接的例子来说明可能发生这种情况的合法案例,但具有讽刺意味的是,未定义的行为很难故意重现。

    【讨论】:

    【解决方案3】:

    我的直接想法是错误,一旦退出if(){},分配给buf[] 的堆栈内存不再保证属于该数组。

    没错。

    但是代码构建和运行没有问题

    未定义的行为。在cout &lt;&lt; charPtr 语句中,charPtr 是一个指向无效内存的悬空指针。内存是否已被物理释放是无关紧要的。内存已超出范围。

    我不擅长组装,但如果我没看错的话,离开花括号后堆栈指针似乎不会被重置。

    没错。

    当进入函数时,数组的内存被预先分配在堆栈帧的顶部(作为sub esp, 1D8h 指令的一部分),然后在函数退出时清理堆栈帧期间被释放(作为add esp, 1D8h 指令的一部分)。

    如您所见,当输入if 时,它所做的第一件事就是调用_memset()[ebp-114h] 处已存在的数组清零。

    但这是一个实现细节,不要依赖它。

    有人可以仔细检查我,并指出这段代码在技术上是否有效?

    不是。

    【讨论】:

    • 非常感谢!
    猜你喜欢
    • 1970-01-01
    • 2011-05-04
    • 2019-01-26
    • 2012-01-10
    • 2014-06-08
    • 1970-01-01
    • 2019-07-15
    • 1970-01-01
    • 2014-10-16
    相关资源
    最近更新 更多