为什么我不应该对未由malloc()分配的变量调用free()?

我在某处读到使用free来摆脱一个不是通过调用malloc创建的对象是灾难性的,这是真的吗? 为什么?

这是未定义的行为 – 永远不要尝试。

让我们看看当你尝试free()自动变量时会发生什么。 堆管理器必须推断如何获取内存块的所有权。 要做到这一点,它必须使用一些单独的结构来列出所有已分配的块,并且这是非常慢的,很少使用或希望必要的数据位于块的开头附近。

后者经常使用,这就是我应该如何工作。 当您调用malloc()时,堆管理器会分配稍大的块,在开头存储服务数据并返回偏移指针。 Smth喜欢:

 void* malloc( size_t size ) { void* block = tryAlloc( size + sizeof( size_t) ); if( block == 0 ) { return 0; } // the following is for illustration, more service data is usually written *((size_t*)block) = size; return (size_t*)block + 1; } 

然后free()将尝试通过偏移传递的指针来访问该数据,但如果指针指向自动变量,那么无论数据位于何处,它都希望找到服务数据。 因此未定义的行为。 很多时候服务数据被free()修改为堆管理器来获取块的所有权 – 所以如果指针传递给自动变量,一些不相关的内存将被修改和读取。

实施可能会有所不同,但您不应做出任何具体假设。 仅对malloc()系列函数返回的地址调用free()

根据标准,它是“未定义的行为” – 即“任何事情都可能发生”。 但这通常是坏事。

在实践中: free指针意味着修改堆。 C运行时实际上永远不会validation传递的指针是否来自堆 – 这在任何时间或内存中都是昂贵的。 结合这两个因素,你得到“免费(非malloced-ptr)将在某处写一些东西” – resutl可能是你背后修改的“你的”数据,访问冲突,或破坏重要的运行时结构,如堆栈上的返回地址。

示例:一个灾难性的场景:
您的堆实现为一个简单的空闲块列表。 malloc表示从列表中删除合适的块,free表示再次将其添加到列表中。 (一个典型的,如果简单的实现)

你释放()指向堆栈上的局部变量的指针。 你是“幸运的”,因为修改进入无关的堆栈空间。 但是,部分堆栈现在位于您的免费列表中。

由于分配器设计和分配模式,malloc不太可能返回此块。 稍后,在程序的一个完全不相关的部分中,你实际上将此块作为malloc结果获取,写入它会在堆栈中删除一些局部变量,并且当返回一些重要指针包含垃圾并且您的应用程序崩溃时。 症状,复制和位置与实际原因完全无关。

调试那个。

这是未定义的行为。 从逻辑上讲,如果行为未定义,您无法确定发生了什么,以及程序是否仍在正常运行。

有些人在这里指出这是“未定义的行为”。 我将进一步说,在某些实现中,这将导致程序崩溃或导致数据损坏。 它与“malloc”和“free”的实现方式有关。

实现malloc / free的一种可能方法是在每个分配的区域之前放置一个小标头。 在malloc’d区域,该标题将包含该区域的大小。 释放该区域时,将检查该标头,并将该区域添加到相应的空闲列表中。 如果发生这种情况,这是个坏消息。 例如,如果释放堆栈上分配的对象,则突然部分堆栈位于空闲列表中。 然后malloc可能会返回该区域以响应将来的调用,并且您将在整个堆栈中乱写数据。 另一种可能性是你释放一个字符串常量。 如果该字符串常量位于只读内存中(通常是这样),则此假设实现将导致段错误并在稍后的malloc之后或者free将对象添加到其空闲列表时崩溃。

这是我正在讨论的假设实现,但你可以用你的想象力来看看它是如何变得非常非常错误的。 某些实现非常强大,并且不容易受到这种精确类型的用户错误的影响。 某些实现甚至允许您设置环境变量以诊断这些类型的错误。 Valgrind和其他工具也会检测到这些错误。

严格来说,这不是真的。 calloc()和realloc()也是free()的有效对象源。 ;)

malloc / free的实现肯定有可能保留已分配的内存块的列表,并且在用户试图释放不在此列表中的块时不执行任何操作的情况下。

但是,由于标准说这不是一个要求,因此大多数实现都会将所有指针都视为有效。

请看一下未定义的行为意味着什么。 符合标准的托管 C实现的malloc()free()是根据标准构建的。 标准说,在malloc() (或包装它的东西,例如calloc() )返回的堆块上调用free()的行为是未定义的。

这意味着,只要您自己对free()进行必要的修改,它就可以执行您想要的任何操作。 你不会通过使malloc()未分配的块上的free()行为一致甚至可能 malloc() 破坏标准。

实际上,可能存在(自己) 定义此行为的平台。 我不知道,但可能有一些。 有几个垃圾收集/日志记录malloc()实现可能会让它在记录事件时更优雅地失败。 但那是实施 ,而不是标准定义的行为。

未定义只是意味着不要指望任何类型的一致行为,除非您自己实现它而不破坏任何已定义的行为 。 最后, 定义的实现并不总是由主机系统定义。 许多程序链接(和运送) uclibc 。 在这种情况下,实现是自包含的,一致的可移植的。