2009-07-03 20 views
2

我有一個關於(在Debian GNU/Linux和GCC 4.3.3)在C內存管理的問題。爲什麼我在釋放指針後解除引用時會得到不同的結果?

根據K & R的C編程語言手冊(第7.8.5章),當我釋放一個指針並將其解引用時,就是一個錯誤。但是我有些疑惑,因爲我已經注意到有時候,正如我在下面粘貼的源代碼那樣,編譯器(?)似乎按照明確定義的原則工作。

我有一個簡單的程序是這樣,那就說明如何返回動態分配的數組:如果我嘗試用一​​些參數來編譯它

#include <stdio.h> 
#include <stdlib.h> 


int * ret_array(int n) 
{ 
    int * arr = (int *) malloc(10 * sizeof(int)); 
    int i; 
    for (i = 0; i < n; i++) 
    { 
     arr[i] = i*2; 
    } 
    printf("Address pointer in ret_array: %p\n", (void *) arr); 
    return arr; 
} 

int * ret_oth_array(int n) 
{ 
    int * arr = (int *) malloc(10 * sizeof(int)); 
    int i; 
    for (i = 0; i < n; i++) 
    { 
     arr[i] = i+n; 
    } 
    printf("Address pointer in ret_oth_array: %p\n", (void *) arr); 
    return arr; 
} 

int main(void) 
{ 
    int *p = NULL; 
    int *x = NULL; 
    p = ret_array(5); 
    x = ret_oth_array(6); 

    printf("Address contained in p: %p\nValue of *p: %d\n", (void *) p, *p); 

    free(x); 
    free(p); 
    printf("Memory freed.\n"); 
    printf("*(p+4) = %d\n", *(p+4)); 
    printf("*x = %d\n", *x); 

    return 0; 
} 

-ansi -Wall -pedantic-errors,它不會引發錯誤或警告。不僅;它也運行良好。

Address pointer in ret_array: 0x8269008 
Address pointer in ret_oth_array: 0x8269038 
Address contained in p: 0x8269008 
Value of *p: 0 
Memory freed. 
*p+4 = 8 
*x = 0 

*(P + 4)爲8和* x爲0。 爲什麼發生這種情況? 如果*(p + 4)是8,不應該* x爲6,因爲x數組的第一個元素是6?

另一個奇怪的事情發生,如果我嘗試改變調用的順序釋放。 如:

int main(int argc, char * argv[]) 
{ 
/* ... code ... */ 

    free(p); 
    free(x); 

    printf("Memory freed.\n"); 
    printf("*(p+4) = %d\n", *(p+4)); 
    printf("*x = %d\n", *x); 

    return 0; 
} 

事實上,在這種情況下,輸出(我的機器上)將是:

*p+4 = 8 
*x = 142106624 

爲什麼X指針真正的「釋放」,而P指針釋放(我希望)「不同」? 好吧,我知道,釋放內存後,我應該使指針指向NULL,但我只是好奇:P

+0

嘗試多次運行,然後比較結果 – Vaibhav 2009-07-03 10:12:15

+0

我已經運行了至少10次,並且總是得到相同的結果 – Markon 2009-07-03 10:49:22

+1

在同一臺機器上的單線程程序中,這些事情往往表現爲可重複的 - 這就是爲什麼他們會導致「在我的機器上工作」 - 錯誤的種類。但是,在它之前/之後添加一些代碼,添加另一個線程,使用不同的編譯器設置,您將得到不同的結果 – Niki 2009-07-03 12:59:11

回答

10

free()(和malloc())不是來自gcc。他們來自C庫,在Debian上通常是glibc。所以,你看到的是glibc的行爲,而不是gcc的(並且會隨着不同的C庫或不同版本的C庫而改變)。

我特別在使用free()之後,你正在釋放內存塊malloc()給你。這不是你的了。由於它不再被使用,glibc中的內存管理器可以隨意使用內存塊來做任何事情,包括使用它的一部分作爲自己的內存結構(這可能是爲什麼你看到它的內容發生了變化;它們已經被記錄信息覆蓋,可能是指向某種其他塊或計數器的指針)。

還有其他的事情可能發生;特別是如果分配的大小足夠大,glibc可以向內核請求單獨的內存塊(使用mmap()或類似調用),並且free()期間將其釋放回內核。在這種情況下,你的程序會崩潰。這在理論上也可能在某些情況下發生,即使分配很小(glibc可以增長/縮小堆)。

13

這是不確定的行爲,所以它是一個錯誤的尊重free d指針奇怪的事情可能(並會)發生。

free()不會更改指針的值,因此它會一直指向進程地址空間中的堆 - 這就是爲什麼您不能獲得段錯誤,但是它沒有被指定,並且在某些平臺的理論上可以獲得當您嘗試在free之後立即解除引用指針時發生段錯誤。

爲防止出現這種情況,在free之後指定NULL指針是個好習慣,因此它會以可預測的方式失敗 - segfault。

請注意,在某些操作系統(HP-UX,也可能是其他一些操作系統)中,允許取消引用NULL指針,以防止段錯誤(從而隱藏問題)。我覺得這很愚蠢,因爲它讓事情變得更加難以診斷,儘管我不知道背後的全部故事。

2

雖然你所看到的行爲似乎是一致的,它不能保證如此。不可預見的情況可能會導致這種行爲發生變化(更不要說這完全依賴於實施)。

具體來說,在您的示例中,您可以釋放()數組,然後在訪問數組時獲取舊內容。如果您在free()之後再添加malloc()調用,那麼舊的內容將會丟失。

2

即使內存是freed,它也不一定會用於其他目的。你的進程內存的舊指針仍然是有效的指針(儘管未分配內存),所以你也不會出現分段錯誤。

9

這可能不是你要找的答案,但我給它一個想試試:

既然你與你永遠不應該以任何方式,形狀或形式取決於未定義行爲打,它知道給定的實現如何處理它有什麼好處?由於gcc在任何給定的時間,版本,體系結構之間或根據月亮的位置和亮度自由地改變處理,因此在知道如何處理它的時候沒有任何用處。至少不要使用gcc的開發者。

3

一旦釋放動態內存變量,它不是你的。內存管理器可以自由地執行它所指向的那塊內存的更好處理。據我所知,編譯器對於釋放的內存塊沒有做任何事情,因爲它是一個函數,而不是由語言定義的。即使它是由語言定義的,編譯器也會將調用插入到底層的OS函數中。

只想說,這是語言不確定的,所以你必須檢查你的操作系統,並在釋放它後觀看那塊內存。行爲可能是隨機的,因爲有時候其他程序會要求記憶,有時候不會!順便說一下,它在我的機器上是不同的,兩個指針的值都是變化的。

4

*(p + 4)是8,* x是0.爲什麼會發生這種情況?如果*(p + 4)是8,不應該* x是6,因爲x數組的第一個元素是6?

對此的一種可能的解釋是printf(「...%i ...」)可能在內部使用malloc爲其字符串插值分配一個臨時緩衝區。那會在第一次輸出後覆蓋兩個數組的內容。

通常,如果程序在釋放指針後依賴指針的值,我會認爲它是錯誤的。我甚至會說,如果它在被釋放後保持指針的值(而不是讓它超出作用域或用NULL覆蓋它),那麼這是一種非常糟糕的代碼異味。即使它在非常特殊的情況下工作(具有特定堆管理器的單線程代碼)。

相關問題