我在某处读到,用它free
来摆脱不是通过调用创建的对象是灾难性的malloc
,这是真的吗?为什么?
7 回答
这是未定义的行为 - 永远不要尝试。
让我们看看当您尝试free()
使用自动变量时会发生什么。堆管理器必须推断如何获得内存块的所有权。为此,它要么必须使用一些单独的结构来列出所有已分配的块并且非常缓慢且很少使用,要么希望必要的数据位于块的开头附近。
后者经常使用,这就是我应该如何工作。当您调用 malloc() 时,堆管理器会分配一个稍大的块,在开头存储服务数据并返回一个偏移量指针。喜欢:
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()
,堆管理器会修改服务数据以获取块的所有权 - 因此,如果传递的指针指向自动变量,则将修改和读取一些不相关的内存。
实现可能会有所不同,但您永远不应该做出任何特定的假设。仅调用家庭函数free()
返回的地址。malloc()
按照标准,它是“未定义的行为”——即“任何事情都可能发生”。不过,这通常是坏事。
在实践中:free
' 指针意味着修改堆。C 运行时实际上从不验证传递的指针是否来自堆——这在时间或内存上都是代价高昂的。结合这两个事实,你会得到“free(non-malloced-ptr) will write something something”——结果可能是一些“你的”数据在你背后修改、访问冲突或破坏重要的运行时结构,例如堆栈上的返回地址。
示例:一个灾难性的场景:
您的堆被实现为一个简单的空闲块列表。malloc 表示从列表中删除合适的块,free 表示将其再次添加到列表中。(一个典型的微不足道的实现)
您 free() 指向堆栈上的局部变量的指针。你是“幸运的”,因为修改进入了不相关的堆栈空间。但是,堆栈的一部分现在在您的空闲列表中。
由于分配器设计和您的分配模式,malloc 不太可能返回此块。后来,在程序的一个完全不相关的部分,你确实得到了这个块作为 malloc 结果,写入它会将一些局部变量丢弃在堆栈中,并且当返回一些包含垃圾的重要指针时,你的应用程序会崩溃。症状、再现和位置与实际原因完全无关。
调试那个。
这是未定义的行为。从逻辑上讲,如果行为未定义,您就无法确定发生了什么,以及程序是否仍在正常运行。
有人在这里指出这是“未定义的行为”。我将进一步说,在某些实现中,这会使您的程序崩溃或导致数据损坏。它与“malloc”和“free”的实现方式有关。
实现 malloc/free 的一种可能方法是在每个分配的区域之前放置一个小标题。在 malloc 的区域上,该标头将包含该区域的大小。当该区域被释放时,会检查该标头并将该区域添加到相应的空闲列表中。如果这发生在你身上,这是个坏消息。例如,如果你释放一个分配在堆栈上的对象,堆栈的一部分突然出现在 freelist 中。然后 malloc 可能会返回该区域以响应将来的调用,并且您将在整个堆栈中乱写数据。另一种可能性是您释放了一个字符串常量。如果该字符串常量在只读内存中(通常是),则此假设实现将导致段错误并在稍后的 malloc 之后或当 free 将对象添加到其 freelist 时崩溃。
这是我正在谈论的假设实现,但您可以发挥您的想象力,看看它是如何变得非常非常错误的。一些实现非常健壮,不易受到这种精确类型的用户错误的影响。一些实现甚至允许您设置环境变量来诊断这些类型的错误。Valgrind 和其他工具也会检测到这些错误。
严格来说,这是不正确的。calloc() 和 realloc() 也是 free() 的有效对象源。;)
请查看未定义行为的含义。malloc()
并且free()
在符合标准的托管C 实现上构建。标准说调用不是由(或包装它的东西,例如)free()
返回的堆块的行为是未定义的。malloc()
calloc()
这意味着,只要您自己进行必要的修改,它就可以做任何您想做free()
的事情。您不会通过使 on 块的行为不是由一致甚至可能有用的分配来打破标准。free()
malloc()
事实上,可能有平台(它们自己)定义了这种行为。我不知道,但可能有一些。有几个垃圾收集/记录 malloc() 实现可能会在记录事件时让它更优雅地失败。但那是实现,而不是标准定义的行为。
未定义只是意味着不要指望任何类型的一致行为,除非您自己实现它而不破坏任何已定义的行为。最后,定义的实现并不总是意味着由主机系统定义。许多程序链接(和运送)uclibc。在这种情况下,实现是自包含的、一致的和可移植的。
/的实现当然有可能保留已分配的内存块的列表,并且在用户尝试释放不在此列表中的块的情况下什么也不做。malloc
free
然而,由于标准说这不是一个要求,大多数实现会将所有进入 free 的指针视为有效。