標籤:

c++中子函數的局部變數在函數結束後是否會銷毀變數的內容?

如題,見圖,照理說當func函數執行完畢後dd這個變數應該是被銷毀了才對啊?為什麼我在主函數輸出*a的時候會是dd中的值1000,我覺得應該是一個未知的值才對啊。。


你得知道「銷毀」是什麼意思。

C++里銷毀的意思是調用析構函數,或者啥都不做。對,出了那個函數,那個變數已經被銷毀了。但它的值不見的變化啊。反正,任何對它的訪問都是未定義行為。所以它等於什麼,不重要。


這麼理解吧,func函數向棧租了間房間,幹完那活以後退房。dd這些個局部變數都是一次性用品,func用過了,現在是垃圾了,func才懶得收拾滿屋的垃圾,退房後直接拍屁股走人。不巧的是房東(編譯器?)也是個懶人,也懶得收拾滿屋子的垃圾,只是把房間標記成空閑可租賃。主函數如果在func退房後趕緊趴在窗戶上看一眼,會發現func用過的東西都還在呢。(逃


有析構函數一定會執行,沒有的話就隨便了


在函數結束後硬要把局部變數搞成一個隨機值是一件沒有意義的事情。這裡只能說函數結束後局部變數的值【不再保證有效】。


是的,你返回了函數中的臨時變數的地址,這個地址在函數返回後,就處在 stack 的後續的分配的方向(stack top)上。毫無疑問的是,這個地址是「可以安全的用於讀寫訪問」的(stack 實際就位於內存中,位於虛擬內存的某個 segment 中,從屬於一個虛擬的進程空間)。

由於這裡只是一個 int 類型的臨時變數,所以函數返回時,是不會去理會這裡的變數裡面存放了什麼內容的,也沒必要做什麼「銷毀」動作。函數返回時,只是把 esp 寄存器調回到正確的指向就結束了,換句話說,只是一條指令:add esp, xxx 就完成了對臨時變數所在空間的回收操作,這時,這些臨時變數所在的位置就位於 stack 之外,stack 開口不遠的位置上,而那些臨時變數依然保留著之前的值。(並等待著隨時被後續的新的分配覆蓋)

所以在你的上層函數中,由於拿到了 stack 開口位置的地址,是可以把這裡的數據讀出來的,而且恰好,還沒有新的分配將它們覆寫,所以你就讀到了它此前的值,儘管該位置已經是「已回收」的狀態。

對於函數內的臨時對象(類的實例),編譯器將會在函數返回前,自動插入對該類的析構函數的調用的代碼。所以,如果你在一個類的析構函數里,把其成員變數清零,那麼這些操作就會被執行到。

int 這種語言內置的基本數據類型,則沒有額外的「析構」動作。


銷毀是需要花點力氣的,退出方法指針將不指向那一片內存,變成空閑區域,隨時會再次被使用。

大部分情況下,你還能輸出,是因為沒人用到了那片空閑區域!然而機器也懶得去銷毀,重置內存也得費電。


這只是個巧合,因為在執行輸出語句時程序還沒有執行別的棧操作,所以原來值還是保留的,沒有被覆蓋掉,如果在這個輸出語句之前再加一條輸出語句就會發現值已經被修改了;

如果要返回局部變數指針可以定義為靜態的;

返回函數里"char *a = "test";"和「char a[] = "test";」的指針才是有區別的。


你是把堆內存和棧內存的行為搞混了吧?堆內存被釋放的時候(某些c庫)將內存統一設為特定值 用於檢測非法寫操作 並不是刻意要銷毀數據。但棧內存在釋放的時候一般啥都不做只是改一下棧指針寄存器。棧內存被用過的區域一般馬上會被下一個函數的腳印覆蓋所以一般不用那種方法(比如你如果再調一個有局部變數的func2再列印a的話值就變了)而且如果是underflow(你的例子的寫操作的情況)根本是無害的因為棧頂再往前本來就是未定義區域。再者,棧內存是隨線程一起分配的 主線程的棧內存整個程序結束前一直都存在而且尺寸也不會變 所以也少了堆內存的內存頁撤銷形成的對歷史值的銷毀。

題外話,這些省掉的事情都是為了效率,但我個人覺得從工程角度來說健壯性和容錯性才是最重要的 所以c能讓人犯那樣的錯可以算是它的缺陷。當然在c++裡面如果你願意你可以在析構里memset


我覺得題主沒有底層模型的概念, 所以知道變數應該是銷毀了, 但對於仍然能訪問, 而且值沒變覺得很奇怪. 首先你得知道dd是分配在棧上的, 調用func的過程就是壓參數,返回地址這些, 在棧上為局部變數開闢空間. 調用結束彈棧, 對於局部變數的銷毀, 就是執行下析構函數, 局部變數的內存(變數本身佔用的內存 == sizeof(variable))的回收就是彈棧的過程. 在調用結束後因為你還有那個局部變數的地址, 你去查看那兒的值就是看棧上那塊內存的內容, 語言的標準說了這是未定義行為, 意味著你的程序的正確性不應該依賴於這個值變或是不變, 因為不同的編譯器可以自己選擇要怎麼處理, 比如可能有編譯器每次彈棧前把不再用的那塊棧空間都填0或是一些奇怪的東西, 這也是完全符合語言的要求的, 當然這樣太浪費了, 2333


這相當於你雞蛋碎在大街上了,你記著位置回去找,還能在地上找到蛋黃。

如果等久一點,再回去找(可能環衛都已經清理過了),那就不一定能找到啥了。


undefined behavior 就是什麼情況都可能發生 無法保證其正確性


你們家有個菜園子,你們家種的都是大白菜,,,

你在城裡打工,讓你媽給你哄孩子去,你媽去的時候就把這個菜園子給了你三嬸他們家種!

……

耶,你媽剛去沒幾天,你媳婦就和你媽不和,你媽賭氣就回了家(當然大多數時候是為你著想,不想讓你為難),,,

那麼問題的關鍵就來了,,,

你三嬸他們全家都不愛吃大白菜啊,所以你媽去你那兒時,她們趕緊拔光了白菜種上了蘿蔔,你媽回來要回菜園子時裡面全是蘿蔔了!

當然,你三嬸有可能還沒來得及拔白菜就趕上你媽賭氣回來要菜園子了!

……

這事兒說去說來還是取決於你媳婦兒,你媳婦兒對你媽越狠,你們家菜園子變蘿蔔的可能性越小!你媳婦兒越和善,你們家還是大白菜的可能性就越小!

『更』:本來我只想闡明一下離開函數時,原先局部變數所佔內存里的內容還在,只是不再保證這個內存里的內容受控(評論區里說的『不再保證有效』),壓棧時會隨時覆蓋掉這段內存里的內容!他老婆越狠,他媽就回來的越快,在還沒有覆蓋掉原先內存內容時,就趕緊查看原內存里的內容,內容肯定還是在的!

在這裡,我向提問者道個歉,向大家道個歉,請大家原諒我這個半道出家的程序員,不該不懂卻誤導人的行為(我的本意是想更形象的比喻讓提問者懂,怪自己學藝不精)

謝謝各位的指點!


這是棧上分配的空間,所謂棧上分配釋放就是sp寄存器的變化,只要不複寫到那個地址,值是不會變的


指針和引用離開作用域不會自動銷毀(


你說這房子我不要了,但地址還在,如果房子沒被拆掉,根據地址去找房子還在啊!如果房子被強拆了,去找房子沒有了,但地址還是一樣的!

局部變數基本類型是放在堆棧上的,房子沒有被拆!如果再調用一個函數,就可能被覆蓋了!

#include&
#include&

int* func() {
int dd = 100;
return dd;
}

int* func2() {
int dd = -100;
return dd;
}

int main()
{
int *a = func();
printf("%d
", *a);
func2();
printf("%d
", *a);
}


我覺得是多少不是關鍵啊,這樣的代碼應該會報錯吧,因為試圖引用一個未經合法申請的內存區域。講道理,這和

*(0x00ffeedd) = 1000;

有什麼區別


推薦閱讀:

c++中在局部空間用new運算符創建的變數是否會被銷毀?
c++怎麼檢測內存泄露,怎麼定位內存泄露?
為什麼我覺得 Objective-C 的內存管理比 C++ 要複雜得多?這類語言是否是趨勢?
C++局部靜態變數的內存什麼時候創建的?
C++允許「我們都是人,所以我可以把你私有的眼睛借來隨便玩,再還給你」,這難道是一種設計上的妥協?

TAG:C |