簡體   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