C++ 关于编译器及其工作方式的问题
这是释放单链表内存的C代码。它是用Visual C++ 2008编译的,代码也应该是这样工作的。C++ 关于编译器及其工作方式的问题,c++,c,compiler-construction,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;
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->next分配给current。但很明显,如果我用其他编译器(例如,书的作者使用的编译器)编译这段代码,程序就会工作。所以问题是,为什么用特定的编译器编译的代码可以工作?是因为编译器把指令放在二进制文件中,记住了当前->下一步的地址,尽管我释放了当前,而我的VC++没有。我只是想了解编译器是如何工作的。第二个例子是错误的代码-它不应该在被释放后引用当前的
current
。在许多情况下,这似乎是可行的,但这是未定义的行为。使用像valgrind
这样的工具将清除这样的bug
请引用您在中看到的任何书籍,因为它需要更正。第二个程序正在调用未定义的行为。这不是编译器的不同,而是C标准库和函数free()实现的不同。编译器将把指针
current
存储为局部变量,但不会存储它引用的内存副本
调用free()时,放弃了传递给free()函数的指针所指向的内存的所有权。有可能在您放弃所有权后,指向的内存内容仍然合理,并且仍然是进程地址空间中的有效内存位置。因此,访问它们可能看起来是可行的(请注意,您可以通过这种方式默默地损坏内存)。一个非空的指针指向已经被放弃的内存,它被称为A,并且非常危险。仅仅因为它看起来有效并不意味着它是正确的
我还应该指出,以捕获这些错误的方式实现free()是可能的,例如在每次分配时使用单独的页面,并在调用free()时取消页面映射(这样内存地址就不再是该进程的有效地址)。这样的实现效率很低,但某些编译器在调试模式下有时会使用它来捕获悬空指针错误。在执行释放(当前)
后,当前
指向的内存(其中存储了当前->下一个)已返回到C库,所以你不应该再访问它了
C库可以随时更改该内存的内容-这将导致current->next
被损坏-但它也可能不会更改部分或全部内容,尤其是在这么短的时间内。这就是为什么它在某些环境中有效,而在其他环境中无效
这有点像开车闯红灯。有时你会侥幸逃脱,但有时你会被卡车辗过。了解编译器如何工作的最好方法不是询问它们如何处理无效代码。你需要读一本关于编译的书(实际上不止一本)。一个开始的好地方是查看参考资料。实际上,这是C运行时,而不是编译器。无论如何,后面的代码包含未定义的行为-不要这样做。它在某些实现上对某人有效,但正如您所看到的,它在您的实现上崩溃得很糟糕。它也会悄悄地破坏某些东西
对于后者可能起作用的原因,可能的解释是,在某些实现中,free()
不会修改块内容,也不会立即将内存块返回到操作系统,因此取消引用指向块的指针仍然是“合法的”,块中的数据仍然完好无损
是因为编译器把指令放在二进制文件中,记住了当前->下一步的地址,尽管我释放了当前,而我的VC++没有
我不这么认为
我只是想了解编译器是如何工作的
下面是一个使用GCC编译器的示例(我没有VC++)
如果它是一个类,并且有一个析构函数,那么在我们使用delete操作符释放对象在内存中占用的空间之前,应该调用它
销毁对象并释放其内存空间后,您不能再引用current->next(当前->下一步)了。请告诉我们这本书,这样我们就可以避免使用它,并建议不要使用它。C primer(所有版本均为第5版、第4版..此错误无勘误表)查看此主题:C运行时用某种模式填充已释放块的字节会发生什么(MSVC的0xfeeefeee)。但这只发生在调试版本中,如果您在发行版中尝试它,它很可能会起作用。这仍然是一个未定义的行为,请放弃任何推荐此的书。C primer plus(所有版本,第五、第四版…)谢谢-我知道印度大学用于C编程的书非常糟糕(Kanetkar、Balaguruswami等)但我猜问题更普遍。将最后4个空闲的地址放入DR0-DR3寄存器并在所有寄存器上设置读取断点可能更容易。
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'