2010-04-14 31 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。