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

ult*_*ohn 9 c memory malloc free memory-management

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

sha*_*oth 15

这是未定义的行为 - 永远不要尝试.

让我们看看当你尝试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;
 }
Run Code Online (Sandbox Code Playgroud)

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

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


pet*_*hen 10

按照标准,它是"未定义的行为" - 即"任何事情都可能发生".但这通常是坏事.

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

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

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

由于分配器设计和分配模式,malloc不太可能返回此块.后来,在程序的完全不相关的部分,你实际上得到这个块作为malloc的结果,写它象垃圾一样清除一些局部变量的堆栈,并返回一些重要的指针,当包含垃圾和你的应用程序崩溃.症状,复制和位置与实际原因完全无关.

调试那个.


Dra*_*ter 6

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

  • 为什么?因为标准说它确实如此.怎么样?我不知道.如果我们知道它是如何发生的,那就不会是不确定的. (3认同)

Die*_*Epp 5

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

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

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