【问题标题】:Trying to use free() to understand how it works尝试使用 free() 来了解它是如何工作的
【发布时间】:2014-05-27 23:08:22
【问题描述】:

为了了解 free 在 C 编程语言中的用法,我尝试在 Ubuntu 上运行此代码,但在运行 EXE 文件时收到 SIGABRT 错误。为什么程序没有正常退出?

#include<stdio.h>
#include<stdlib.h>

int main()
{
   int ret;
   int *ptr;
   ptr = (int *)malloc(sizeof(int)*10);
   free(ptr);
   ptr = &ret;
   free(ptr);
   return 0;
}

【问题讨论】:

  • 静态分配自动回收。不需要free它。
  • 你不需要在 C 程序中强制转换 malloc 的返回值。
  • 您永远不会将malloc()calloc()realloc() 未返回的值传递给free,或者向您返回指向“好像”分配的内存的指针的函数通过这些函数之一(例如strdup()getline()asprintf()scanf()%ms 修饰符等)。
  • 您是如何在 Ubuntu 上获得并运行 EXE 文件的?
  • free 是“un-malloc”函数,而不是“free any memory”函数。

标签: c debugging pointers malloc free


【解决方案1】:

尝试释放您没有从malloc(或其朋友之一)获得的指针会导致未定义的行为。您的第二个free(ptr) 电话就是这样做的。

来自 C 规范,§7.22.3.3 free 函数,第 2 段:

free 函数使ptr 所指向的空间被释放,即可供进一步分配。如果 ptr 是空指针,则不会发生任何操作。否则,如果参数与内存管理函数先前返回的指针不匹配,或者如果空间已通过调用 freerealloc 释放,行为未定义。

【讨论】:

    【解决方案2】:

    虽然所有未定义行为的答案都是正确的,但查看实现可能会更有帮助。

    我认为K&R C malloc 是一个很好的参考。 "The C Programming Langugage",第 8.7 章对它的工作原理进行了解释。

    请记住,在查看来自 C 标准库函数的代码时,它们具有实现和规范,其中实现可能具有其规范所不需要的可重现行为。

    基本上几乎所有malloc 实现都有一个空闲列表,它们在其中管理一个列表(或多个列表)中的空闲内存区域。空闲列表通常以某种方式处理,在内存区域上多次调用free 会使该列表处于不正确的状态。

    当有目的地构建数据结构时,也可以调用恶意行为。 Phrack 有一篇过时的文章,介绍了如何在将无效内存传递给 free 并破坏空闲列表时调用代码执行。

    Other malloc implementations 可能也值得一看(这是分配库的不完整列表)。

    【讨论】:

      【解决方案3】:
         ptr = &ret;
         free(ptr);
      

      等同于:

          free(&ret);
      

      ret 驻留在堆栈上,而不是堆上。尝试在堆上未分配的free() 内存(使用malloc() 等)将导致未定义的行为。

      【讨论】:

      • 在我熟悉的每一种情况下,都会导致运行时错误。
      • @RiaD:我工作过的每个平台都会出现运行时错误。但是平台不是必须的(例如,在没有操作系统的情况下运行的非常受限的嵌入式系统),并且未定义的行为被广泛误解(blog.regehr.org/archives/213),这意味着与“您的操作系统会抱怨”非常不同。
      • @MaxLybbert,好吧,我的评论解决了答案中的“将导致运行时错误”行,现在已被编辑掉
      • 我知道答案已经存在了一段时间,我没想到你或@MahonriMoriancumer 会给出答案。我只是想说明“未定义的行为”比“突然崩溃”意味着更多。这可能意味着“编译器更改了您的代码,最终结果甚至可能无法用原始语言描述。”
      【解决方案4】:

      函数free() 仅适用于由malloc()heap 上分配的内存。不适用于静态分配,因为静态分配是自动处理的。这是错误:

       int ret; 
      
       ptr = &ret;
      
       free(ptr);
      

      您不能这样做,因为ret 的内存没有在堆上分配。它在堆栈中,应该只释放堆的内存。

      【讨论】:

        【解决方案5】:

        您的第二个free(ptr) 正在导致未定义的行为,因为您试图释放您未分配的指针。另请注意,静态分配会自动回收,因此您不需要它们来释放它。

        【讨论】:

          【解决方案6】:
            ptr = &ret;
             free(ptr);
          

          在这里,您试图释放本地存储堆栈变量的内存。根据规则,您不应该释放它。当 main() 退出堆栈上的所有本地存储时,内存总是空闲的。

          Free 仅适用于堆分配内存。

          【讨论】:

            【解决方案7】:

            在 c 或 c++ 程序中可以在三个区域创建变量。

            • 全局或静态变量位于可执行二进制文件中的固定位置。
            • 静态范围之外的自动变量在堆栈中
            • malloc'ed 或 calloc'ed 变量在堆上。

            free() 是释放堆上先前分配的内存的函数。 malloc 或类似函数返回指向堆上内存的指针。读取或写入该内存的唯一方法是通过指针。指针是地址,指针* 是指向该地址的内容。

            在显示的示例中,有两个变量,定义在 Main 中,实际上是静态的,以及来自 Main 的返回值,它在堆栈上。使用最小整数,16 位,这是一个可能的内存映射。在此映射中,指令从 0 开始,堆栈从某个非零值(堆栈的开头 - bos)开始并通过递增而增长,并且堆从最大地址(...FFFF,又名 -1)开始并通过递减而增长:

            (记住,MIN_INT 是 -32768,MAX_INT 是 32767...规范只保证 16 位,有符号)

            每个字节都有一个“n”位宽的地址 - 通常为 16、32 或 64 位


            -1。 (堆开始,例如16位地址:0xFFFF,32位地址:0xFFFFFFFF或64位地址:0xFFFFFFFFFFFFFFFF)

            -2。 (从堆开始向下的第一个位置。0x...FFFE)ptr[9],一次

            -3。 (从堆开始向下的第二个位置。0x...FFFD)

            -4。 (从堆开始向下的第三个位置。0x...FFFC)ptr[8],一次

            [剪辑]

            -17。 (从堆开始向下的第 16 个位置。0x...FFEF)

            -18。 (从堆开始向下的第 17 个位置。0x...FFEE)ptr[1],一次

            -19。 (从堆开始向下的第 18 个位置。0x...FFED)

            -20(从堆开始向下的第 19 个位置。0x...FFEC)ptr[0],一次

            -21。 (堆顶,从堆开始向下 10 X 16 位整数。0x...FFEB),一次

            : 32 位或 64 位机器上的地址范围非常大... :

            tos: (栈顶 0x...tos)

            bos + ( sizeof( int ) - 1) 从 Main() 返回的 int 结束

            bos:(栈的开始:在静态数据之上)从Mail()返回的int的开始

            togs:(全局/静态顶部)“ptr”结束

            :(指针的大小是地址总线的宽度......不管它需要什么)

            togs-(n-1): (top of global/static - (sizeof( int* ) - 1)) “ptr”开始

            (togs-n) : “ret”结束

            (togs-n)-1: "ret" 的开始

            (编译器为自己、调试器等添加的任何全局内容)

            (程序代码结束)

            (程序代码的开始)

            (非程序代码的顶部)

            0(非程序代码开始,0x...0000)


            在运行时,“ptr”和“ret”可能都从“0”开始,因为它们是固定的、静态的值,从可执行二进制文件来源的文件中读出。

            随着程序的运行,“ptr”的值发生变化,首先,指向堆,在 malloc 的 10 个整数数组:“0x...FFEC”

            对 free() 的调用不会改变 ptr 的值,它仍然是“0x...FFEC” 释放“0x...FFEC”是合法的,并且运行起来没有任何有趣的东西。

            赋值“ptr = &ret”将新值设置为“ptr”,“(togs-n)-1”,“ret”的开始。

            释放“(togs-n)-1”会导致立即崩溃,因为“free”会检查“(togs-n)-1”的值,并且它不在堆地址的有效范围内。

            "ret" 保持空白,从未设置,但由于它是全局/静态的,它保持在链接器将其写入磁盘时的任何状态。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-01-11
              • 2017-03-04
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多