free一塊修改過的malloc指針會發生什麼?

int *p = (int *) malloc(10*sizeof(int));
p++;
free(p);

請問這樣一來,是不是釋放了當前位置和後面的9個int的空間?前面的1個int的空間是不是就沒法處理了?

初學者、昨晚睡前偶爾想到的問題,今早單獨試了一下,發現編譯連接都沒報錯,一運行就出現Debug Assertion Failed!

int *p = (int *) malloc(10*sizeof(int));
int *q = p;
p++;
free(q);// 這樣寫沒有錯.

int *p = (int *) malloc(10*sizeof(int));
p++;
free(p);// 這樣寫編譯連接沒有錯! 一運行就出現 Debug Assertion Failed!

感謝各位的回答!

後來在《程序設計教程:用C++語言編程》陳家俊, 鄭濤編著. 機械工業出版社. 第3版. 第172頁看到了這種兩種情況:

  1. 需要特別注意的是,不能用delete和free撤銷非動態變數,否則產生程序異常錯誤。例如:

    int x, *p;
    p = x;
    delete p;//Error

  2. 另外,用delete和free撤銷動態數組時,其中的指針變數必須指向數組的第一個元素!例如:

    int *p = new int[n];
    p++;
    delete []p;//Error

    但是書上並沒有講不能這樣做的原因,再次感謝各位的詳細解答。


我先問你個問題:指針中是沒有所在內存塊大小的信息的,那麼 free 怎麼才能知道要釋放的內存塊有多大呢?

於是,對於大多數內存分配器,malloc 申請的實際內存比你要求的空間要大幾個位元組,裡面存儲了額外的數據來記錄這塊內存有多大,一般就是直接存在指針左邊。free 的時候,就會去讀取指針地址減去一個常數之後的那塊內存,來獲取內存塊的信息。

因此如果你 free 一個不指向內存塊開始處的指針,free 的時候就會把其他的數據錯誤解釋成內存塊的信息,(大概率)導致程序崩潰。

當然現代的內存分配器對於不同大小的內存申請,會採用不同的分配策略,但無論策略如何,去 free 一個不是 malloc 來的指針,都是非常危險的舉動。


@Belleve 提到的是從一般的實現方式來考慮這個問題,但標準中沒有指明要這樣實現的。幾個答案中也只提及 malloc(),所以我想補充一下。

其實這類問題,查一下標準庫的說明就可以,例如 free - cppreference.com

void free(void *ptr);

Deallocates the space previously allocated by malloc(), calloc(), aligned_alloc or realloc().

If ptr is a null pointer, the function does nothing.

The behavior is undefined if the value of ptr does not equal a value returned earlier by malloc(), calloc(), realloc(), or aligned_alloc().

??

簡單翻譯:

釋放之前由 malloc()、calloc()、aligned_alloc() 或 realloc() 所分配的空間。

若 ptr 為空指針,此函數不做事情。

若 ptr 的值並非等於之前由 malloc()、calloc()、aligned_alloc() 或 realloc() 返回的值,行為未定

義。

??

題目中的問題在於把 malloc() 返回的值修後再調用 free(),按說明就是未定義行為。完。


這種行為未定義。


看書要看經典,TCPL 是把malloc的實現當例子講過的,如果你學C語言看的是這本書,就根本不會問出這樣的問題。


初學者的話,不要多想,把CSAPP看了再說。

C是一門設計感不是很強的語言,換句話說很多東西就是沒什麼道理和規律可循的,要知道具體的問題和當時的寫法才能明白其中道理。

比如吧,malloc是個內存管理package,並不是一個很底層的東西。 更加底層的看,一個程序有一大塊內存,叫作heap。但是呢,heap的大小是有限的,用完了問操作系統要更多heap,是很耗費資源的,於是就需要內存管理系統來幫助整理heap,提高利用率。

這個時候就要在內存裡面建立一個鏈表,把沒有用的內存塊,大小等等都儲存起來,方便按照大小取用。隨著內存塊被malloc和被free,這些free block也會被打散和合併。之所以可以做到,就是因為默認每個內存塊的首尾都記錄有信息。

你任意在內存塊里移動,free的時候程序自動往前讀一個block,發現的卻不是一個完整的tag(你自己放了什麼就是什麼),assertion failure已經是小事了,基本上會segfault,運氣不好你會跳到別的內存塊去,損壞一大片內存。

所以,不要亂搞。CSAPP里自己寫過一個malloc就明白了。


malloc分配的區塊是帶有metadata的,裡面記錄了區塊的大小等必要的信息。malloc返回的指針經過free調用的時候會先找到metadata再進行釋放,也就是說如果你餵給free一個別的指針的話那肯定是會報錯的


除非你能自己實現這兩個函數,不然就不要在這兩個函數的用法上自己創新,想要釋放一部分,用realloc。


記住malloc返回什麼,free就傳入什麼,除此之外的其他行為請後果自負,寫C語言要按規定來,編譯成功不代表運行就ok,C語言最忌諱「舉一反三」的自作聰明了


首先是你的做法屬於 undefined 範圍

其次是與實現有關係

在 OS X 下 maclloc free 由 libSystem_malloc.dyld 實現 基於 malloc_zone

每一個 zone 對應一個 meta data 當你使用 malloc 系申請內存 則相應的 zone meta data 會做記錄 包括指針 分配大小等信息

當 free 執行 會先去找到相應的 zone 和 meta data 從而找到該回收的內存大小 但是你改變指針後 這個紀錄在 meta data 里匹配不上 就算你指針指向的確實是一塊已分配的空間 但是依然會報錯 pointer being freed was not allocated 也就是說 只要匹配不上 就是認為未分配。

當然你可以自己改一下 malloc 實現 可是這有什麼意義呢?


樓上的各位大大都已經說的很清楚了,在C語言中使用malloc分配內存要注意:

The behavior is undefinedif the value of ptr does not equal a value returned earlier by malloc(), calloc(), realloc(), or aligned_alloc().

題主有興趣可以看一下libc庫中關於虛擬內存管理的具體實現。對於不同的實現(比如dlmalloc和jemalloc),在你的例子中會得到不一樣的結果.


這樣操作是錯的。

malloc分配的內存除了你指定的長度外還有額外的頭部記錄分配的內存的信息,用於分配內存的管理。

返回的指針是p,釋放時用的指針必須與分配時返回的指針相同,否則Segmentfault可不是喜聞樂見的東西。


手機碼字不便,錯漏之處還望提醒

==============================

題主的問題,其實有點亂,主要涉及到兩個問題,第一: malloc 和 free, 第二: new 和 delete 以及 new[] 和 delete[]. 第一個問題用 c 語言的知識就可以回答,第二個語言用到 c++ 的知識,因為它們只在c++中存在。

1. 首先需要了解的是,malloc 或者 new 在堆中分配內存,堆中分配的內存需要自己手動調用 free 或者 delete 去釋放. 而我們在函數中定義的變數是在棧中分配內存,函數調用結束自動回收,不需要我們手動釋放。如果你對於堆和棧的概念還不理解,你可以去看一看俞甲子和石凡寫的《程序員的自我修養》這本書。

所以

int a, *p;

p = a;

delete a;

這種方式是完全錯誤的,因為你根本沒有手動在堆中分配內存,自然不應該手動歸還。

2. 第一個問題涉及 malloc 和 free. 這兩個函數使用一定要成對,而且你傳給 free 的參數一定要是 malloc 返回的地址。

要理解為什麼

int *p = malloc(10*sizeof(int));

p++;

free(p);

會有有問題,你要需要理解 malloc 到底做了什麼。它除了分配了空間之外,還需要簿記工作。比如下面代碼(c 語言中malloc的值可以不用進行強制轉換,為了碼字方便後文全部省略)。

int *a = malloc(10*sizeof(int));

int *b = malloc(5*sizeof(int));

free(a);

free(b);

要知道,我們 malloc 的時候提供了大小信息,free的時候卻沒有,很顯然,這個信息必然在 malloc 請求之後存放在某個位置,這樣我們 free 才知道需要回收多少內存。

一種普遍的做法是放在返回給用戶的地址之前。也就是說如果 malloc 返回 1024 這個地址,其實,在 1024 之前比如 1020 處存放了大小信息。當你調用free的時候,這個函數會自動找到1020這個位置讀取出大小信息,然後正確的處理地址歸還。再看看上面那段代碼你就會發現大錯特錯。因為 p++ 之後 free 認為有大小信息的地方(1024,假設int為4位的話)根本沒有,它會強制把不是大小信息的內容解釋為大小信息,所以程序陷入了未定義行為。

如果你對這些還不理解,你看著看一看c語言之父寫的書《c程序設計語言》的最後一章(如果沒記錯的話),裡面有一個 malloc 和 free 的實現. 看完自然會理解。

3. 關於 new 和 delete 以及 new[] 和 delete[]. 首先注意的是,這是兩組不同的操作符,不能混用。

要知道 new 不僅僅是分配了內存,它還會調用構造函數。當然對於基礎類型來說構造函數什麼也不做,所以 new 基本等同malloc.而 delete 除了釋放內存之外還需要調用析構,同樣對於基礎類型來說,析構什麼也不做,所以 delete 基本等同於 free.但是對於用戶自定義類型來說兩者就會有很大的差別。如果你對上面這些不理解可以參考李普曼寫的《深度探索c++對象模型》一書。

理解了 new 和 delete, 那麼對應的 new[] 和 delete[] 自然好理解。因為 delete[] 需要調用析構函數,所以很明顯它需要知道到底需要調用幾次。這個信息沒有包含在 delete[] p 中,所以很明顯,系統內部也同樣做了簿記信息,記錄 new[] 到底分配了幾個對象,而這一信息通常存放在返回的地址之後,所以

int *p = new int[n];
p++;
delete []p;//Error

的錯誤和前面說到的 free 的錯誤是同一個道理。

最後需要提醒的是,在c++中,我們通常不會手動調用delete或者delete[]因為很難做到異常安全,更合理的方式是使用 RAII 管理資源。如果不理解,你可以看一看《effective c++》一書中相關內容。


自己寫過過malloc和free的不請自來。malloc返回的指針前面會有一個int header用於標記block的大小和allocation,free(int *p) 會修改p--的內容用來標記該block可用。所以如果free的parameter不是block的開頭,那free就會修改p--的內容(裡面是你存的東西),結果就是炸。


wildpoint:

你說如果你想賣房子,賣掉自己的房子當然沒問題,但是如果你只是一個租客但是想賣房東的 房子,那麼不論是司法還是房東,你都會陷入一個trouble , 我們俗稱 bug.

double free:

如果你將你的房子賣了一次,你覺得賣便宜了,然後你將你之前的房子賣一次,那麼這又會是一個bug。

說來說去,就是先看看你的手上到底有沒有房子,這個房子是不是你自己的,如果沒房子或者租別人的房子,你手上只有房子的地址,你就想賣了他,你說這是不是Bug呢?

指針變數就是一個地址,一個房子的地址,XXXX路XXXX號XXXX小區XXXX樓

而內存 就是這個地址對應的房子里的空間。

一句話,我住在光華路世紀小區88號樓。

這句話只是我給你傳遞了一個信息(指針變數),你可以通過我給你的信息(指針變數)找到我的房子(內存)。

當然我也可以搬家,那麼我的地址(指針變數)也就發生改變,如果你去我之前的地址找我(訪問已經被釋放的內存),那個地址的人肯定會告訴你你找錯地方了(編譯器報錯)。


等等填坑(′?ω?`)

-------------

一直忘記填坑了…剛剛翻了下知乎才想起來

字就不打了,詳情參考Linux/Unix系統編程117頁第七章7.1.3節 malloc()和free()的實現。

我還是直接把圖貼出來吧…


操作系統殘忍的發現,申請好返回的指針和你提交給他釋放的指針不一樣,他必然坐不住了,要和你理論一番。。。。


在百度上搜 「狗拿耗子 + dlmalloc」。既然題主問了這個問題,看來是對程序的運行感興趣。推薦「狗拿耗子」系列文章,該系列詳細介紹了程序的編譯鏈接以及運行過程。


申請的時候實際上佔用的內存要比申請的大。因為超出的空間是用來記錄對這塊內存的管理信息。

大多數實現所分配的存儲空間比所要求的要稍大一些,額外的空間用來記錄管理信息——分配塊的長度,指向下一個分配塊的指針等等。這就意味著如果寫過一個已分配區的尾端,則會改寫後一塊的管理信息。這種類型的錯誤是災難性的,但是因為這種錯誤不會很快就暴露出來,所以也就很難發現。將指向分配塊的指針向後移動也可能會改寫本塊的管理信息。

malloc()申請的空間實際就是分了兩個不同性質的空間。一個就是用來記錄管理信息的空間,另外一個就是可用空間了。而用來記錄管理信息的實際上是一個結構體。

struct mem_control_block {

int is_available;

int size;

};

void free(void *ptr) {

struct mem_control_block *free;

free = ptr - sizeof(struct mem_control_block);

free-&>is_available = 1;

return;

}


寫過一篇new/delete和malloc/free的文章,可以參考下:https://github.com/q00148943/LaTeX/blob/master/delete/codediff%20之內存泄露.pdf


malloc函數分配的內存是通過一個結構體管理的,free的時候是通過指針值來確定對應的結構體的,你修改了指針值就無法找到相應的結構體,就相當與free了一個非malloc分配的指針。

個人理解,歡迎糾正。


一切理論都不如你去看malloc和free的源碼來得直接。我在網上找到看過,你看下就好。


1.free操作參數必須是malloc的內存起始,這也是你free(q)沒有問題的原因

2.即使不考慮free的問題,p++之後也不是僅釋放了9個sizeof(int),而是10個sizeof(int)-1


推薦閱讀:

為什麼基礎很好的程序員代碼依舊寫的很爛?
有哪些適合編程的筆記本電腦值得推薦?
從語言設計的角度來看, Pascal 是一門優秀的語言嗎?
c++有提供網路編程,多線程編程之類的庫嗎?
安卓編程筆記本求推薦?

TAG:編程 | C編程語言 | CC |