2010-04-14 72 views
12

這是釋放單個鏈表的內存的C代碼。它使用Visual C++ 2008進行編譯,並且代碼正常工作。關於編譯器及其工作原理的問題

/* Program done, so free allocated memory */ 
current = head; 
struct film * temp; 
temp = current; 
while (current != NULL) 
{ 
    temp = current->next; 
    free(current); 
    current = temp; 
} 

但我也遇到過(甚至在書)這樣寫的相同代碼:

/* Program done, so free allocated memory */ 
current = head; 
while (current != NULL) 
{ 
    free(current); 
    current = current->next; 
} 

如果我編譯的代碼與我的VC++ 2008,程序崩潰,因爲我第一次釋放電流,然後分配當前 - >下一個電流。但顯然,如果我用其他編譯器(例如,本書作者使用的編譯器)編譯此代碼將會起作用。所以問題是,爲什麼這個代碼是用特定的編譯器工作編譯的?是否因爲編譯器把指令放在二進制文件中,記住current-> next的地址,儘管我釋放了current,而我的VC++卻沒有。我只想了解編譯器是如何工作的。

+2

哪些書是那些? – 2010-04-14 10:31:33

+4

@尼爾,壞的。 – 2010-04-14 10:34:04

+10

請告訴我們這本書,這樣我們可以避免它,並建議反對它。 – 2010-04-14 10:37:19

回答

18

第二個程序調用未定義的行爲。它不是編譯器的區別,而是C標準庫和函數free()的實現方式的差異。編譯器會將指針current存儲爲局部變量,但不會存儲它所引用的內存的副本。

當您調用free()時,您放棄了傳遞給free()函數的指針所指向的內存的所有權。有可能在放棄所有權後,指向內存的內容仍然合理,並且仍然是進程地址空間中有效的內存位置。因此,訪問它們可能會起作用(請注意,您可以以這種方式默默地損壞內存)。非空的指向已經被放棄的內存的指針被稱爲dangling pointer,它非常危險。僅僅因爲它似乎可行,並不意味着它是正確的。

我還應該指出,可以通過捕獲這些錯誤的方式實現free(),比如每個分配使用一個單獨的頁面,並且在free()被調用時取消映射頁面(這樣內存地址不再是該進程的有效地址)。這樣的實現非常低效,但有時在調試模式下被某些編譯器用來捕獲懸掛指針錯誤。

+1

將最後4個自由空間的地址放入DR0-DR3寄存器並在所有寄存器上放置一個讀斷點可能會更容易。 – MSalters 2010-04-14 12:52:16

3

第二個例子是錯誤的代碼 - 它在釋放後不應該引用current。這似乎在許多情況下工作,但它是未定義的行爲。使用像valgrind這樣的工具將清除像這樣的錯誤。

請引用您看過本例的任何一本書,因爲它需要更正。

+0

http://bytes.com/topic/c/answers/212665-freeing-simple-linked-list C primer plus(所有版本,第5,第4 ...) – dontoo 2010-04-14 10:53:47

+0

謝謝 - 我知道印度的書籍大學對C編程的使用相當糟糕(Kanetkar,Balaguruswami等),但我想這個問題更爲廣泛。 – 2010-04-14 11:05:57

11

做完free(current)之後,current指向的內存(其中current->next已存儲)已返回到C庫,因此您不應再訪問它。

C庫可以隨時更改該內存的內容 - 這將導致current->next被破壞 - 但它也可能不會更改其中的一部分或全部內容,特別是很快。這就是爲什麼它在某些環境下工作,而不是其他人。

這就像駕車穿過紅色交通燈。有時你會擺脫它,但有時你會被卡車碾過。

4

找出編譯器如何工作的最好方法不是詢問他們如何處理無效代碼。編譯時需要閱讀一本書(實際上是一本書)。開始的一個好地方是查看Learning to write a compiler的資源。

1

實際上,這是C運行時,而不是編譯器。無論如何,後者的代碼包含未定義的行爲 - 不要這樣做。它在某些實現上爲某人工作,但正如你所看到的那樣,它會在你的系統上迅速崩潰。它也可以默默地損害某些東西。

可能的解釋,爲什麼後者可能會工作的是,在某些實現free()不會修改塊的內容,並不立即返回到操作系統的內存塊,所以取消引用塊的指針仍然是「合法「,塊中的數據仍然完好無損。

0

是因爲編譯器把指令放在二進制文件中,記住current-> next的地址,儘管我釋放了current,而我的VC++卻沒有。

我不這麼認爲。

我只是想了解編譯器是如何工作的。

這是我們釋放對象所佔的空間之前,GCC編譯器的例子(我沒有VC++)

struct film { film* next; }; 

int main() { 
    film* current = new film(); 
    delete current; 

    return 0; 
} 

;Creation 
movl $4, (%esp) ;the sizeof(film) into the stack (4 bytes) 
call _Znwj  ;this line calls the 'new operator' 
        ;the register %eax now has the pointer 
        ;to the newly created object 

movl $0, (%eax) ;initializes the only variable in film 

;Destruction 
movl %eax, (%esp) ;push the 'current' point to the stack 
call _ZdlPv  ;calls the 'delete operator' on 'current' 

如果這是一個類,並有一個析構函數,那麼它應該被稱爲在刪除操作符的內存中。

破壞對象並釋放其內存空間後,您無法再安全地引用current-> next。