酒店房间和 C++ 局部变量的作用域
問題:Can a local variable’s memory be accessed outside its scope? 有一段局部變量的內(nèi)存,可以從其范圍之外訪問它么?
如下代碼:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | int *foo() { ????int a = 5; ????return &a; } int main() { ????int *p = foo(); ????cout << *p; ????*p = 8; ????cout << *p; } |
這樣的代碼可以正常執(zhí)行,而且沒有任何運行時的異常!
輸出是 5 8
這是怎么回事?難道局部變量在函數(shù)外也可以被訪問嗎?
?
來自微軟資深軟件工程師?Eric Lippert?的最佳答案(3200+贊):
你在酒店里租了一間房。你把一本書放進了桌子的第一個抽屜里,然后就去睡覺了。當你第二天早上醒來時,你假裝忘記去還鑰匙了。你偷了房間的鑰匙!
一周之后,你回到了酒店,但沒有入住,你用偷來的鑰匙溜進了你上次入住的房間,并查看了那個抽屜。你的書還在那里。是不是很令人吃驚!
這是怎么回事呢?難道一個酒店房間的抽屜不是應(yīng)該無法被一個沒有入住這個房間的人看到嗎?
好吧,明顯的是,這種情況在真實世界中當然會發(fā)生。在你不入住這個房間的時候,這里面沒有任何神秘的力量把你的書弄消失掉,也沒有魔法能夠阻止你用偷來的鑰匙進入房間。
酒店的管理規(guī)章里沒有要求拿走你的書。你也沒有跟他們說如果你落下了一本書,他們可以幫你撕毀它。如果你用偷來的鑰匙非法進入了你上次的房間,并且沒有被酒店的安保系統(tǒng)發(fā)現(xiàn)。你也沒有跟他們說如果你之后嘗試溜進房間,他們應(yīng)該阻止你。不過事實上,你確實簽了一份協(xié)議規(guī)定你保證不會偷偷溜回房間。只不過你打破了協(xié)議。
在這種情況下任何事情都有可能發(fā)生。如果你運氣好的話,那本書可能還在那里。其他人的書也可能在那個抽屜里而你的書則被丟進了酒店的火爐里。也可能當你溜進去的時候正好有個人在把你的書撕成碎片。酒店可能把那張桌子連帶你的書都移走了,而把一個衣柜放在那里。這家酒店也可能正好要被拆除,換成一個足球場,在你溜來溜去的時候。你可能會在一場爆破中死去。
當你離開酒店而偷了房間的鑰匙的時候,你不知道將會發(fā)生什么。你放棄了去生活在一個可靠的,安全的世界里,因為你選擇去打破系統(tǒng)的規(guī)則。
C++不是一門安全的語言。你可以非常輕松就打破這個系統(tǒng)的規(guī)則。如果你嘗試去做一些非法并且愚蠢的事情,比如你回到那個你已經(jīng)不入住的房間,并想要去查看那張也許已經(jīng)不存在的桌子。C++不會阻止你的。比C++更加安全的語言通過限制你的能力來解決這個問題,比如通過更加嚴格的控制房間鑰匙。
【更新】:
我的老天。這個答案獲得了這么多的關(guān)注。(我不知道為什么,我只是覺得這樣比喻比較有趣, 不過管他呢。)
我認為在經(jīng)過了更加技術(shù)性的思考之后更新一下這個答案是必要的。
編譯器的工作是生成代碼來管理這個程序數(shù)據(jù)擁有的內(nèi)存。有很多方式來生成管理內(nèi)存的代碼,但是這么多年來有兩個基本的技術(shù)是必須要知道的。
第一個是擁有一片長期存在的區(qū)域,這片存儲區(qū)域里的每一個字節(jié),他們的生命周期比較長。生命周期的意思就是它們能夠被程序訪問的時期。這類內(nèi)存沒辦法提前進行預(yù)估。編譯器生成一種叫堆管理器的代碼,它知道如何在需要的時候動態(tài)的分配內(nèi)存,當內(nèi)存不再被需要的時候釋放掉他們。
第二個是擁有一片短期存在的區(qū)域,這片存儲區(qū)域里的每一個字節(jié)都可以提前進行預(yù)估。而且比較特殊的是,這片區(qū)域的生命周期遵循一種嵌套模式。也就是說,在這片區(qū)域中擁有最長生命周期的變量,它所分配的內(nèi)存地址被它之后分配的那些生命周期較短的變量所重用。
局部變量就是第二種情況。當調(diào)用一個函數(shù)時,它的局部變量便被生成了。當這個函數(shù)調(diào)用另外一個函數(shù)時,新函數(shù)的局部變量也被生成了。這些變量會在第一個函數(shù)的局部變量之前被釋放掉。這些局部變量的內(nèi)存地址的開始和結(jié)束可以提前被計算出來。
因為這個原因,局部變量經(jīng)常被分配到棧數(shù)據(jù)結(jié)構(gòu)里,因為一個棧的特點是第一個入棧的元素將會最后一個出棧。
這就好像酒店決定只能按照順序進行房間的出租。你沒辦法離開,除非你之前所有房間號比你大的人都走了。
所以,讓我們來想一下棧的操作過程。在很多操作系統(tǒng)中,每一個線程都有一個棧,并且棧的大小是一個可變的確定大小。當你調(diào)用一個函數(shù)的時候,相關(guān)的內(nèi)容被壓入棧內(nèi)。當你把一個這個棧的指針傳出這個函數(shù)時,就像上面的提問者所干的一樣。那個指針只是指向全部有效的數(shù)百萬個字節(jié)內(nèi)存塊的中間。在我們的類比中,當你離開酒店的時候,你只是離開了當前被占用的數(shù)字最大的房間。如果沒有人在你之后入住,你又非法地回到了這個房間。你所有的東西肯定都還在這個酒店的房間里。
我們用棧作為臨時存儲因為它們非常廉價并且容易實現(xiàn)。C++的實現(xiàn)沒有規(guī)定一定要用棧來存儲局部變量,你可以使用堆來存儲它們,不過沒有人這么干,因為那樣做會使得程序變得很慢。
C++也沒有規(guī)定在你離開棧之后需要清掉棧里的內(nèi)容,所以你可以在之后非法地回到棧里找到你之前的內(nèi)容。當然編譯器如果生成代碼,一旦你不再使用了就把棧里的所有內(nèi)容都清零,這是完全合法的。不需要再解釋為什么了,因為這樣做代價非常高。
C++沒有規(guī)定要確保當棧變小時,之前有效的內(nèi)存地址依然有效。C++的實現(xiàn)也允許告訴操作系統(tǒng)“我們已經(jīng)不再需要棧的個內(nèi)存頁了。除非我說,否則當有任何人要訪問這個之前有效的棧的內(nèi)存頁的時候拋出一個異常并結(jié)束程序”。再次,一般的實現(xiàn)也沒有這么做,因為這么說使程序變慢而且沒有必要。
相反,大多數(shù)時候,一般的C++實現(xiàn)允許你犯錯然后避免它。直到有一天,一些真正非常令人恐怖的錯誤出現(xiàn)了然后把整個程序弄崩潰了。
這樣做是有問題的。C++里有如此多的規(guī)則而又如此輕易就可以打破它們。我自己就有好多次這樣的經(jīng)歷。更糟的是,這種問題往往是表面的,當你發(fā)現(xiàn)內(nèi)存地址沖突了之后去檢查內(nèi)存,卻發(fā)現(xiàn)它們在很長時間內(nèi)又是正確的。所以你很難知道到底是哪個地方出錯了。
那些內(nèi)存安全的語言通過限制你的能力來解決這個問題。在規(guī)范的C#里,沒有任何辦法去獲取一個局部變量的內(nèi)存地址,然后返回它或者是存儲它等以后再用。你可以獲取一個局部變量的內(nèi)存地址,但是語言被很好的設(shè)計了,你不可能在局部變量生命周期之后還能夠使用它。為了取得局部變量的內(nèi)存地址并把它返回,你必須要把編譯器設(shè)置為一個特殊的不安全的模式,并且在你的程序里寫上“unsafe”關(guān)鍵字。這可以幫助提醒你,你正在做一些不安全的可能會打破規(guī)則的事情。
更進一步閱讀:
當C#返回引用時做了些什么?
http://blogs.msdn.com/b/ericlippert/archive/2011/06/23/ref-returns-and-ref-locals.aspx
為什么我們用棧來管理內(nèi)存?C#里值的類型是否一直存儲在棧里?虛擬內(nèi)存是如何工作的?以及更多的關(guān)于C#內(nèi)存管理是如何工作的。這里許多文章都對C++程序員有幫助。
http://blogs.msdn.com/b/ericlippert/archive/tags/memory+management/
總結(jié)
以上是生活随笔為你收集整理的酒店房间和 C++ 局部变量的作用域的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 艾融软件中签率何时公布
- 下一篇: 中原银行京东金融联名卡可以取现么?这些费