关于编译器及其工作原理的问题

时间:2010-04-14 10:25:27

标签: c++ c compiler-construction

这是释放单链表的内存的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旁边分配current->。但显然如果我用其他编译器(例如,书籍作者使用的编译器)编译此代码,程序将起作用。所以问题是,为什么用特定编译器编译的代码工作?是因为编译器将指令放在记住current->的地址的二进制文件中,然后我释放了当前而我的VC ++没有。我只想了解编译器的工作原理。

6 个答案:

答案 0 :(得分:18)

第二个程序正在调用未定义的行为。它与编译器没有区别,而是C标准库和函数free()的实现差异。编译器将指针current存储为局部变量,但它不会存储它引用的内存副本。

当你调用free()时,你放弃了传递给free()函数的指针所指向的内存的所有权。在放弃所有权后,指向的内存内容仍然是合理的,并且仍然是进程地址空间中的有效内存位置。因此,访问它们可能会起作用(请注意,您可以通过这种方式静默地破坏内存)。指向非空并且指向已经放弃的内存的指针称为dangling pointer并且非常危险。仅仅因为它似乎有效并不意味着它是正确的。

我还应该指出,可以以捕获这些错误的方式实现free(),例如每次分配使用单独的页面,并在调用free()时取消映射页面(以便内存地址不再是该进程的有效地址)。这样的实现非常低效,但有时在某些编译器处于调试模式时会用来捕获悬空指针错误。

答案 1 :(得分:11)

执行free(current)后,current指向的内存(其中存储了current->next)已返回到C库,因此您不应再访问它。 / p>

C库可以随时更改该内存的内容 - 这将导致current->next被破坏 - 但它也可能不会改变部分或全部内容,特别是这么快。这就是为什么它适用于某些环境,而不是其他环境。

这有点像驾驶红色交通灯。有时你会逃脱它,但有时候你会被卡车碾过。

答案 2 :(得分:4)

了解编译器如何工作的最佳方法不是询问它们如何处理无效代码。您需要在编译时阅读一本书(实际上不止一本)。入门的好地方是查看Learning to write a compiler的资源。

答案 3 :(得分:3)

第二个例子是错误的代码 - 它在释放后不应引用current。这似乎在许多情况下都有效,但它是未定义的行为。使用像valgrind这样的工具可以清除这样的错误。

请引用您在此示例中看到的任何书籍,因为需要更正。

答案 4 :(得分:1)

实际上这是C运行时,而不是编译器。无论如何,后一个代码包含未定义的行为 - 不要这样做。它适用于某些实现的人,但是当你看到它在你的身上崩溃时。它也可以默默地破坏某些东西。

对于后者可能起作用的可能解释是,在某些实现上free()不会修改块内容并且不会立即将内存块返回给操作系统,因此取消引用指向块的指针仍然是“合法的”,块中的数据仍然完好无损。

答案 5 :(得分:0)

  

是因为编译器将指令放在记住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'

如果它是一个类并且有一个析构函数,那么应该在我们使用delete运算符释放对象在内存中占用的空间之前调用它。

在摧毁物体并释放其存储空间后,您无法再安全地参考当前的>