[英]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.