为什么在释放指向它的指针后仍然可以访问结构的成员?

时间:2013-05-09 17:25:33

标签: c pointers memory-management struct

如果我定义一个结构......

struct LinkNode
{
  int node_val;
  struct LinkNode *next_node;
};

然后创建一个指向它的指针......

struct LinkNode *mynode = malloc(sizeof(struct LinkNode));

...然后终于免费()它......

free(mynode);

...我仍然可以访问结构的'next_node'成员。

mynode->next_node

我的问题是:哪一个底层机制跟踪这个内存块应该代表一个结构LinkNode的事实?我是C的新手,我预计在我使用指向我的LinkNode的指针上的free()之后,我将无法再访问该结构的成员。我期待某种“不再可用”的警告。

我希望更多地了解基础流程的工作原理。

6 个答案:

答案 0 :(得分:6)

已编译的程序不再具有struct LinkedNode或名为next_node的字段或类似内容的任何知识。任何名称都已从编译的程序中完全消失。编译程序按数值运算,可以起到内存地址,偏移量,索引等作用。

在您的示例中,当您在程序的源代码中读取mynode->next_node时,会将其编译为机器代码,该代码只从一些保留的内存位置读取4字节数值(称为变量{{1在你的源代码中),向它添加4(它是mynode字段的偏移量)并在结果地址(即next_node)读取4字节值。如您所见,此代码按整数值(地址,大小和偏移量)运行。它不关心任何名称,例如mynode->next_nodeLinkedNode。它不关心是否分配和/或释放内存。它并不关心这些访问是否合法。

(我在上面的示例中重复使用的常量4特定于32位平台。在64位平台上,它将在大多数(或所有)实例中被8替换。)

如果尝试读取已释放的内存,则这些访问可能会导致程序崩溃。或者他们可能不会。这是一个纯粹的运气问题。就语言而言,行为未定义。

答案 1 :(得分:4)

没有,你不能。这是未定义行为的经典案例。

当您有未定义的行为时,任何事情都可能发生。它甚至可能看起来有效,但仅在一年后随机崩溃。

答案 2 :(得分:4)

它运气纯粹,因为释放的记忆尚未被其他东西覆盖。释放内存后,您有责任避免再次使用它。

答案 3 :(得分:2)

底层内存的任何部分都不会跟踪它。它只是编程语言为内存块提供的语义。你可以,例如将它转换为完全不同的东西,仍然可以访问相同的内存区域。然而,这里的问题是,这更有可能导致错误。特别是类型安全将会消失。在你的情况下,仅仅因为你调用free并不意味着底层内存根本就没有了。操作系统中只有一个标记,表示该区域再次免费。

以这种方式思考:free - 函数类似于“最小”内存管理系统。如果您的呼叫需要的不仅仅是设置一个标志,那么它将引入不必要的开销。此外,当您访问该成员时,您(即您的操作系统)可以检查该内存区域的标志是否设置为“空闲”或“正在使用”。但这又是开销。

当然,这并不意味着做那些事情是没有意义的。它可以避免很多安全漏洞,例如在.Net和Java中完成。但是那些运行时比C小很多,而且我们现在有更多的资源。

答案 4 :(得分:2)

当您的编译器将C代码转换为可执行的机器代码时,会丢弃大量信息,包括类型信息。你写的地方:

 int x = 42;

生成的代码只是将某个位模式复制到某个特定的内存块(一个块通常可能是4个字节)。您无法通过检查机器代码来判断内存块是int类型的对象。

同样,当你写:

if (mynode->next_node == NULL) { /* ... */ }

生成的代码将通过取消引用另一个指针大小的内存块来获取指针大小的内存块,并将结果与​​系统的空指针表示(通常为全位 - 零)进行比较。生成的代码并不直接反映next_node是结构的成员的事实,或者关于结构如何分配或者是否仍然存在的任何事实。

编译器可以在编译时检查很多东西,但它不一定生成在执行时执行检查的代码。作为程序员,你应该首先避免犯错误。

在此特定情况下,在致电free后,mynode具有不确定的值。它没有指向任何有效的对象,但是没有要求实现对该知识做任何事情。调用free不会破坏已分配的内存,只是通过将来调用malloc使其可用于分配。

实现可以通过多种方式执行这样的检查,如果在free之后取消引用指针,则会触发运行时错误。但是C语言不需要这样的检查,并且它们通常没有实现,因为(a)它们会非常昂贵,使得程序运行得更慢,并且(b)检查无论如何都无法捕获所有错误。

定义了C,以便在程序完成所有操作时,内存分配和指针操作将正常工作。如果您在编译时发现可以检测到的某些错误,编译器可以对其进行诊断。例如,将指针值分配给整数对象至少需要编译时警告。但是其他错误(例如取消引用free d指针)会导致程序具有未定义的行为。作为一名程序员,你应该首先避免犯这些错误。如果你失败了,那就是你自己。

当然有些工具可以提供帮助。 Valgrind就是其中之一;聪明的优化编译器是另一个。 (启用优化会使编译器对代码执行更多分析,这通常可以使其诊断出更多错误。)但最终C不是一种掌握您的手的语言。它是一个很好的工具 - 可用于构建更安全的工具,例如执行更多运行时检查的解释语言。

答案 5 :(得分:0)

您需要为mynode-> next_node:

指定NULL

mynode-> next_node = NULL;

释放内存后,

表示您不再使用已分配的内存。

如果不指定NULL值,它仍然指向先前释放的内存位置。