#include<stdio.h>
#include <stdlib.h>
int *lvret(void) {
int ret = 5;
return &ret;
}
int main(void) {
int *p = lvret();
printf("%d\n",*p);
}
編譯運行這個程序的結果很可能是5。
所以函數結束後,局部變量的數據仍然可用是嗎?
再來考慮下麵這個程序:
#include<stdio.h>
#include <stdlib.h>
int *lvret(void) {
int ret = 5;
return &ret;
}
void mod(void) {
int a = 7;
}
int main(void) {
int *p = lvret();
mod();
printf("%d\n",*p);
}
這個程序運行的結果很可能是7。
顯然,這個內存地址現在變得不那麼可靠了。
(編譯器還是會給出警告,比如gcc 4.8)
warning: function returns address of local variable [-Wreturn-local-addr]
return &ret;
On a call to free, memory is released and unmapped from the process address space using munmap. This system is designed to improve security by taking advantage of the address space layout randomization and gap page features implemented as part of OpenBSD's mmap system call, and to detect use-after-free bugs—as a large memory allocation is completely unmapped after it is freed, further use causes a segmentation fault and termination of the program.
這是一個很複雜的問題,我談談自己的理解。
首先你需要知道,進程使用的都是虛擬地址空間,每個進程都有獨立的,完整的4GB(32bit下)地址空間,未必每一塊內存都會映射到物理內存,這個映射工作是操作係統完成的。如果你訪問了地址空間裏未映射的內存,或者寫了隻讀的區域,操作係統就會報錯(Segment Fault錯誤,嗬嗬~)並終止你的程序。
當一個進程開始運行時,會向操作係統申請一塊“堆”內存,由程序自己對這塊堆內存進行管理,malloc就是從這塊內存中分配內存,在C語言中,這個申請和管理堆內存的工作是由運行庫自動完成的。
當free一塊內存後,free(即運行庫)會將這塊內存標記為未使用,下次有可能會將這塊內存分配出去。但這塊內存對進程來說仍然是可以讀寫的,因為運行庫已經向操作係統申請,自己來管理這塊內存了。
局部變量是分配在棧上的,進程開始運行時,操作係統會分配給進程一塊固定大小(通常是1MB)的棧,所謂分配和釋放局部變量,都隻是在移動棧頂指針而已,隻要沒超過棧的這1MB內存區域,都還是可以讀寫的。
以上都是常見操作係統的典型行為,也許在某些操作係統和平台上並非這麼工作,總之,使用已釋放的內存是非常危險的行為。
有興趣可以讀這兩本書《深入理解計算機係統》《鏈接裝載與庫》
十幾年前上學的時候,計算機還是緊缺資源,在沒有購買個人電腦之前,我們通常會去學校的計算機室上機。計算機室有專人管理,規矩諸多,其中包括“不要隨意修改係統配置文件”,“不要做和學習無關的事情”等等。而我們經常會很開心地先把autoexec.bat/config.sys大改一氣(很沒出息是吧,不久幾十K字節的事兒麼,:)),然後再往機器上拷貝個金庸群俠傳之類的,偷偷玩上半個小時。第二天再去上機的時候,直奔昨天保存文件的目錄。運氣好的時候,文件還在,於是大喜,接著進度繼續玩;運氣不好的時候,不但文件已經被刪,而且還發現機器上新增的病毒好厲害,->_->。
說這個陳芝麻爛穀子,也許你已經明白我的意思。
其實我想說的就是兩點:
1. 你可以不遵守規則,但不等於沒有規則。
2. 不遵守規則而產生的後果是不可預測的(undefined)。
樓主沒有明確說明為什麼會認為free的內存仍然可用,以及為什麼認為局部變量的數據仍然可用。
為了說明問題,我假設以下的程序:
編譯運行這個程序的結果很可能是5。
所以函數結束後,局部變量的數據仍然可用是嗎?
再來考慮下麵這個程序:
這個程序運行的結果很可能是7。
顯然,這個內存地址現在變得不那麼可靠了。
(編譯器還是會給出警告,比如gcc 4.8)
所以這個例子告訴我們,你能夠訪問到的內存空間並不總是安全的。
換句話說,你發現釋放後的內存數據或者局部變量占用的內存仍然可以讀寫,隻不過是偶然的情況 -- 剛好沒有被別的程序動過而已。
C語言並不是一個內存安全的語言。
C 也不是,但C 11已經好很多了(接納了smart pointer)。
補充
以上主要說明樓主描述的操作為什麼從根本上是需要避免的。
再來補充回答一下樓主的疑問:
問題1:
是Heap(堆)的管理。
樓主的潛台詞應該是“既然內存釋放了,那麼在訪問的時候為什麼不出現
segmentation fault
”。 回答是 -- 這是C運行庫實施層麵的問題。大多數運行庫的實施不會試圖去識別那些已經被"free"的內存塊,並把它們退回係統(所謂退回係統,就是取消在進程地址空間上的映射)。因此,在訪問這些地址的時候,segmentation fault
沒有如預料中出現。 但並不全是這樣,也有例外,比如說OpenBSD就是一個。訪問wiki,你可以看到如下描述:這也從側麵證明了樓主觀察到的現象是不可靠的。
(至於為什麼多數運行庫要采取這樣的內存管理策略,又是另一個話題了)
問題2:
是Stack(棧)的管理。
@精英王子 已經說明了。
內存管理有以下幾個層次(從高到低):C程序 - C庫(malloc)- 操作係統 - 物理內存
首先,操作係統保證每個進程都有獨立的虛擬內存空間(32bit上應該是4G吧,一般進程也用不了這麼多)。當然實際上物理內存是所有進程共享的,所以當你需要動態內存時,需要向操作係統申請,這時候雖然從你程序的角度,內存是連續的,其實是被操作係統映射到某一塊物理內存而已。程序用完內存歸還後,實際歸還的部分可能被操作係統分配給其他進程。
要注意,上麵說的“歸還”是malloc庫的行為。malloc庫會使用一些策略來提高內存使用的效率,比如程序需要使用10K內存時,malloc實際可能上會申請1M,因為一次係統調用開銷很大;再比如即使你調用了
free
“歸還“了程序使用的內存,malloc庫也可能並未真正把這些內存歸還給操作係統,因為將來程序可能還會再申請動態內存。malloc庫有多種實現,我知道的一種是使用標記(tag)來存儲內存的元信息。比如你申請了8個byte,得到的頭指針地址是0x1001(實際內存為0x1001-0x1008),malloc會在0x1000(也就是頭指針-1的位置)保存8,即這段內存的長度。等釋放時,程序將頭指針地址傳給
free
,malloc庫從頭指針-1的位置發現需要釋放的內存長度,釋放內存(實際的操作可能隻是將tag清空)。這就解釋了:1. 為什麼和malloc
不同,free
的參數隻有一個頭指針而不需要長度;2.free
後內存實際上可能並未歸還給操作係統。所以,訪問被(程序)釋放的內存是一種undefined行為,就是說結果是不確定的。在malloc庫未將此內存歸還給操作係統也未進行下一次動態分配時,這塊內存事實上仍屬於程序。而當malloc庫不清理歸還的內存時(多數實現都是如此),你能訪問到的值仍是原來的值。這和函數調用完畢而未清理棧幀、後續調用函數可以訪問到之前已經設置的局部變量值是一個道理。
但是,當malloc庫已經將內存歸還給係統時,再去訪問原來的地址(更別說寫),由於這段地址已經不屬於程序了,就會出現經典的segmentation fault。
說到底,這些現象還是C語言庫為了更有效率的實現而妥協的結果。
ls 說的很詳細了,在釋放內存時一般建議把指針置空,這樣來避免使用到已釋放的內存。如:
free()和malloc()用一些數據結構(主要是鏈表)管理從堆中分配的內存,但它們隻是庫函數,真正改變堆邊界的係統調用是sbrk()。malloc()發現當前內存不足夠分配時,會先調用sbrk()擴大堆的邊界。
free()釋放內存實際是數據結構中把該部分標記為未使用,但它實際還在堆中,所以還能用。當有大量堆內存都沒有使用,大部分的實現會收縮邊界,這時,釋放掉的內存就不能用了。
Linux下Glibc的內存管理機製大致如下:
從操作係統的角度看,進程的內存分配由兩個係統調用完成:brk和mmap。brk是將數據段(.data)的最高地址指針edata往高地址推,mmap是在進程的虛擬地址空間中找一塊空閑的。其中,mmap分配的內存由munmap釋放,內存釋放時將立即歸還操作係統;而brk分配的內存需要等到高地址內存釋放以後才能釋放。也就是說,如果先後通過brk申請了A和B兩塊內存,在B釋放之前,A是不可能釋放的,仍然被進程占用,通過TOP查看疑似”內存泄露”。默認情況下,大於等於128KB的內存分配會調用mmap/mummap,小於128KB的內存請求調用sbrk(可以通過設置MMMAP_THRESHOLD來調整)。
轉自:http://www.nosqlnotes.net/archives/105
以及:http://bbs.csdn.net/topics/330179712
http://blog.csdn.net/cinmyheart/article/details/38136375
去看這個,有快捷導航小標題的,隻看9.9節即可,看完,你就知道malloc怎麼管理虛擬內存了,god bless you.