当您的编译器将您的 C 代码翻译成可执行的机器代码时,会丢弃很多信息,包括类型信息。你写的地方:
int x = 42;
生成的代码只是将某个位模式复制到某个内存块(通常可能是 4 个字节的块)中。您无法通过检查机器代码来判断内存块是 type 的对象int
。
同样,当您编写时:
if (mynode->next_node == NULL) { /* ... */ }
生成的代码将通过取消引用另一个指针大小的内存块来获取一个指针大小的内存块,并将结果与系统表示的空指针(通常全位为零)进行比较。生成的代码不直接反映next_node
作为结构成员的事实,或者关于结构如何分配或它是否仍然存在的任何信息。
编译器可以在编译时检查很多东西,但它不一定会在执行时生成代码来执行检查。作为程序员,首先要避免犯错取决于您。
在这种特定情况下,在调用 之后free
,mynode
具有不确定的值。它不指向任何有效的对象,但没有要求实现使用该知识做任何事情。调用free
不会破坏分配的内存,它只是使其可用于将来调用malloc
.
实现可以通过多种方式执行这样的检查,如果在free
ing 之后取消引用指针,则会触发运行时错误。但是 C 语言不需要这样的检查,并且它们通常不会被实现,因为 (a) 它们会非常昂贵,使您的程序运行得更慢,并且 (b) 检查无论如何都无法捕获所有错误。
C 的定义是,如果您的程序一切正常,内存分配和指针操作将正常工作。如果您犯了可以在编译时检测到的某些错误,编译器可以诊断它们。例如,将指针值分配给整数对象至少需要一个编译时警告。但是其他错误,例如取消引用free
d 指针,会导致您的程序具有未定义的行为。作为程序员,首先要避免犯这些错误取决于您。如果你失败了,你就靠自己了。
当然,有一些工具可以提供帮助。Valgrind 就是其中之一。聪明的优化编译器是另一个。(启用优化会导致编译器对您的代码执行更多分析,这通常可以使其诊断出更多错误。)但最终,C 并不是一种掌握您的语言的语言。它是一种锋利的工具——可以用来构建更安全的工具,例如执行更多运行时检查的解释语言。