標籤:

全局指針變數指向棧上的對象的問題?

先上代碼

#include &
using namespace std;

class Test
{
public:
explicit Test(int i):a(i){cout&<&<"Construct "&<&a is "&<&a&<&a = 2;

cout&<&<"over"&<&

運行結果

Construct
Destruct
t has destructed
testp-&>a is 1
over

這裡就看出了一點問題,testp是一個全局指針,而t只是一個棧上的對象,testp指向了這個對象,但是棧上的對象在生存周期結束後執行了析構操作,但通過全局指針仍然能對這段地址進行訪問進行訪問,並且,在整個程序結束後也不會再次執行析構操作,那麼到底t這個棧上對象析構後真的釋放了這段內存嗎?為什麼數據還是可以訪問並且沒有變化呢?


你把房間歸還給業主,但持著之前備份鑰匙還是可進去拾放物品的,不過這是非法的,裡面的東西也可能已經被取走,純粹看運氣。遵守合約,維護社會穩定。


根據 C++ 標準,這是 undefined behavior。

When the end of the duration of a region of storage is reached, the values of all pointers representing the address of any part of that region of storage become invalid pointer values. Indirection through an invalid pointer value and passing an invalid pointer value to a deallocation function have undefined behavior. http://eel.is/c++draft/basic.stc#4

你可以試試把 Test::a 換成 string 或 vector 類型,看看會發生什麼。

也可以用 clang 的 AddressSanitizer (Clang 7 documentation) 幫你找錯:

$ clang++-6.0 -fsanitize=address ub.cc
$ ./a.out
ERROR: AddressSanitizer: stack-use-after-scope


你找汽車租賃公司租了輛車;租到車後你偷偷配了把車鑰匙;然後你把車還給了租車公司。

現在,你拿著這把車鑰匙當然還可以開這輛車;但你憑什麼覺得自己的行為不是盜竊?

分配內存=簽訂租車合同

使用分配給自己的內存=按合同使用租給你的車

歸還內存=合同完成,雙方結算清楚,舊合同不再有約束關係

使用已歸還的內存=盜用他人財物

已歸還的內存里,你之前放進去的東西還在=已還回去的汽車裡,你弄髒的座套還沒來得及換;運氣好說不定你還能見到租這輛車的新顧客的錢包呢——反正你早就是個罪犯了,就別裝無辜了。


ub 了。沒變化是因為你析構函數沒有做任何時機釋放資源的行為,然後編譯器剛好沒有把其他變數分配在這塊空間上,所以碰巧看起來是這樣


因為就算析構了,系統也只是把棧對象所在的內存標記成未使用而已——你強行用全局指針變數當然還可以訪問它,而且因為你這是析構完了馬上就訪問原來棧對象所在的內存,所以仍然能看到原來的棧變數——這個時候並沒有別的變數或者棧對象在使用這塊內存。

但是如果是複雜一點的程序,這塊內存可能過一段時間就分給別的變數用了,你這麼做就容易搞出一些莫名其妙的問題。


如果從彙編的角度來看,右大括弧結束後會把t這個對象彈出棧,所謂的彈出棧對應的就是一個pop指令,而所謂的pop指令等價於「將棧頂指針向棧底移動sizeof(t)的大小」,原來的t的內容其實還在內存中,只要你不往棧里push新的內容,那這個地址的內容就不會變


我想到rust中的生命周期,就是防止你胡來的


樓主例子的棧內存分配和使用在同一棧幀,所以值仍然保留,

x86-64 下的情況(只是解釋下可能的情況,標準沒有規定棧幀的分配和釋放)

main 函數只分配了一次棧幀,樓主的代碼里嵌套了 t 對象,雖然縮短了 t 的生命周期,但整個 main 函數是使用了同一份棧幀。所以沒有觸發使用已釋放內存的詭異現象。

(gdb) x /60i $rip-8
0x5555555549da &: push rbp
0x5555555549db &: mov rbp,rsp
0x5555555549de &: sub rsp,0x10 ; main 函數只分配了一次棧幀
=&> 0x5555555549e2 &: mov rax,QWORD PTR fs:0x28
0x5555555549eb &: mov QWORD PTR [rbp-0x8],rax
0x5555555549ef &: xor eax,eax
0x5555555549f1 &: lea rax,[rbp-0xc]
0x5555555549f5 &: mov esi,0x1
0x5555555549fa &: mov rdi,rax
0x5555555549fd &: call 0x555555554b2e &
0x555555554a02 &: lea rax,[rbp-0xc]
0x555555554a06 &: mov QWORD PTR [rip+0x20172b],rax # 0x555555756138 &
0x555555554a0d &: lea rax,[rbp-0xc]
0x555555554a11 &: mov rdi,rax
0x555555554a14 &: call 0x555555554b72 &
0x555555554a19 &: lea rsi,[rip+0x229] # 0x555555554c49
0x555555554a20 &: lea rdi,[rip+0x2015f9] # 0x555555756020 &<_ZSt4cout@@GLIBCXX_3.4&>
0x555555554a27 &: call 0x555555554870 &<_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt&>
0x555555554a2c &: mov rdx,rax
0x555555554a2f &: mov rax,QWORD PTR [rip+0x20159a] # 0x555555755fd0
0x555555554a36 &: mov rsi,rax
0x555555554a39 &: mov rdi,rdx
0x555555554a3c &: call 0x555555554880 &<_ZNSolsEPFRSoS_E@plt&>
0x555555554a41 &: lea rsi,[rip+0x212] # 0x555555554c5a
0x555555554a48 &: lea rdi,[rip+0x2015d1] # 0x555555756020 &<_ZSt4cout@@GLIBCXX_3.4&>
0x555555554a4f &: call 0x555555554870 &<_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt&>
0x555555554a54 &: mov rdx,rax
0x555555554a57 &: mov rax,QWORD PTR [rip+0x2016da] # 0x555555756138 &
0x555555554a5e &: mov eax,DWORD PTR [rax]
0x555555554a60 &: mov esi,eax
0x555555554a62 &: mov rdi,rdx
0x555555554a65 &: call 0x5555555548b0 &<_ZNSolsEi@plt&>
0x555555554a6a &: mov rdx,rax
0x555555554a6d &: mov rax,QWORD PTR [rip+0x20155c] # 0x555555755fd0
0x555555554a74 &: mov rsi,rax
0x555555554a77 &: mov rdi,rdx
0x555555554a7a &: call 0x555555554880 &<_ZNSolsEPFRSoS_E@plt&>
0x555555554a7f &: mov rax,QWORD PTR [rip+0x2016b2] # 0x555555756138 &
0x555555554a86 &: mov DWORD PTR [rax],0x2
0x555555554a8c &: lea rsi,[rip+0x1d4] # 0x555555554c67
0x555555554a93 &: lea rdi,[rip+0x201586] # 0x555555756020 &<_ZSt4cout@@GLIBCXX_3.4&>
0x555555554a9a &: call 0x555555554870 &<_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt&>
0x555555554a9f &: mov rdx,rax
0x555555554aa2 &: mov rax,QWORD PTR [rip+0x201527] # 0x555555755fd0
0x555555554aa9 &: mov rsi,rax
0x555555554aac &: mov rdi,rdx
0x555555554aaf &: call 0x555555554880 &<_ZNSolsEPFRSoS_E@plt&>
0x555555554ab4 &: mov eax,0x0
0x555555554ab9 &: mov rcx,QWORD PTR [rbp-0x8]
0x555555554abd &: xor rcx,QWORD PTR fs:0x28
0x555555554ac6 &: je 0x555555554acd &
0x555555554ac8 &: call 0x555555554890 &<__stack_chk_fail@plt&>
0x555555554acd &: leave
0x555555554ace &: ret

想觸發這種情況,可以改寫一個例子:

main()

調用一個函數:分配棧幀,並賦值給全局變數,釋放棧幀

調用別的函數:會把剛剛的棧幀位置的內容複寫

這時全局變數指向的就是所謂的未定義內容了

#include &
using namespace std;

class Test
{
public:
explicit Test(int i):a(i){cout&<&<"Construct "&<&a is "&<&a&<&a = 2;

cout&<&<"over"&<&

不應該依賴編譯器實現的好,如果有個編譯器給上面 main 函數的嵌套花括弧給新建一個棧幀,則會有情況,怎麼說還是個不好的假設。

CWE-562: Return of Stack Variable Addresscwe.mitre.org

硬廣:

在線演示www.sourcebrella.com


釋放不等於清楚,釋放的意思是這塊內存我不用了,等我高興了我會再撿起來用。

你搞得這個東西叫野指針,它指向不知道什麼地方。

簡單來說就是,你現在看沒什麼變化,如果你的程序能運行足夠長的時間,欄位數據早晚會變成別的。


這是不健壯的代碼 隨時可能掛


你在如家酒店住了一晚,臨走前偷偷安了個遠程監控頭,雖然你退房了,但是裡面的情況你還是可以看得一清二楚。這個功能其實還沒有指針強大,通過指針你還是可以修改那片內存的內容,通過攝像頭你除了看,什麼都幹不了


一句話:離開櫃檯概不負責


testp指向的地址你取出來指不定是什麼值呢,這種情況只是碰巧沒有新值寫入那個地址


#include &
using namespace std;

class Test
{
public:
explicit Test(int i):a(i){cout&<&<"Construct "&<&a is "&<&a&<&a is "&<&a&<&a is "&<&a&<&

再運行一下試試


推薦閱讀:

TAG:CC | C指針 |