【问题标题】:Freeing the stack释放堆栈
【发布时间】:2018-06-01 10:20:30
【问题描述】:

所以我知道在堆栈中分配的变量上调用free() 会导致无效指针错误。

malloced 指针中,malloc() 在实际指针之前分配 8 个字节以留下有关其大小的信息。所以我想知道我是否在一个结构之前创建了一个long,然后在该结构上调用 free 是否可以释放该结构(当然,这是假设这 8 个字节的分配是唯一的malloc 所做的额外事情)。

我想我的最后一个问题是堆栈变量分配和堆分配之间是否有任何真正的区别(就后端对内核的调用而言)。

【问题讨论】:

  • 在实际指针之前分配 8 个字节以留下有关其大小的信息。 - 您正在对其实现进行假设
  • @AndreiTumbar 不太确定您的问题是什么。如果你想欺骗free 认为你传递给它的指针以前是mallocd,-嗯,这很可能,看看你的 libc 如何检查给定的指针。但在最好的情况下,它会破坏 malloc 堆。有什么意义?
  • @AndreiTumbar free 不是万能的;您有责任将以前的 mallocd 指针传递给它。如果你不这样做,事情就会变糟。至于堆栈,你不需要做任何事情。一旦函数返回,堆栈就会重置为调用该函数之前的状态。
  • @AndreiTumbar 不,真的不是。堆栈只是平面缓冲区,支持堆栈的 CPU 具有指向当前“顶部”的寄存器。编译器为您生成该“顶部”的修改。这就是它如此便宜的原因 - 它始终存在,并且“释放”它始终是每个函数一个操作,无论您实际使用了多少堆栈空间。
  • @AndreiTumbar 我不知道你在说什么。如果编译器决定使用寄存器来存储变量 - 它显然不是“在堆栈上”。但是,是的,如果我们谈论的是“自动存储”——那么编译器会根据目标体系结构和许多其他因素(包括程序员的提示,如 register 关键字)来决定将它放在哪里。我认为 C 标准根本没有提到堆栈,它只是非常常见的方法。

标签: c memory-management


【解决方案1】:

一些 C 实现可能会在分配空间之前使用数据来帮助他们管理空间。有些没有。有些人为某些大小的分配而不是其他人这样做。如果他们这样做,它可能是八个字节,或者可能是其他数量。您不应依赖这方面的任何行为。

当您在块中声明long 对象和某种struct 时,编译器可能会将它们放在堆栈上,也可能不会将它们放在一起。它可能会将long 放在struct 之前,反之亦然,或者,因为它优化了您的程序,它可能会将long 保存在寄存器中并且根本不将其放入堆栈,并且它可能会做其他事情事物。在某些C 实现中,long 是八个字节。在某些情况下,事实并非如此。没有好的方法可以确保将两个单独的对象放在相邻的内存中。 (您可以通过将它们放在更大的结构中来使它们不分离。)

即使您能够将longstruct 拼凑在一起,您怎么知道将什么值放入long? C 实现是否将分配的长度放在那里?或者它是指向另一个块的指针?或者对于 C 实现用来跟踪分配的内存的数据库的其他部分?如果mallocfree 正在使用分配空间之前的内存,则该内存不是空的。它需要有一些价值,而你不知道那是什么。

如果幸运的话,将struct 的地址传递给free 可能不会立即使您的程序崩溃。但是从某种意义上说,你已经释放了堆栈的一部分。当您再次调用malloc 时,您返回的指针可能是针对该内存的,然后您的程序可能会写入该空间。那么当你的程序调用其他例程导致堆栈增长到那个空间时会发生什么?您将重叠使用相同的内存。您的某些数据会覆盖其他数据,您的程序将无法运行。

是的,在栈上分配的内存和从堆上分配的内存是有区别的。这超出了 C 呈现给您的程序的模型。但是,在进程具有堆栈和堆的系统中,它们通常位于进程内存中的不同位置。特别是,随着堆栈的增长和缩小,堆栈内存必须保持可用。你不能在不破坏东西的情况下将它与堆混合。

最好就尝试各种事情时会发生什么提出问题。但是,mallocfree 的现代实现非常复杂,您几乎必须接受它们作为您无法轻松查看的服务。相反,为了帮助您学习,您可以考虑以下几点:

  • 您将如何编写自己的mallocfree

  • 编写一些使用malloc分配大量内存的代码,比如一个兆字节,并编写两个称为MyMallocMyFree的例程,它们的工作方式类似于mallocfree,除了它们使用您分配的内存。当MyMalloc 被调用时,它会切出一块内存。当MyFree 被调用时,它会返回该块以使其再次可用。

  • 编写一些实验性代码,随机调用各种大小的MyMallocMyFree,顺序有些随机。

  • 您如何才能完成所有这些工作?你如何将兆字节分成块?你如何记住分配了哪些块,哪些是空闲的?当有人打电话给MyFree,你怎么知道他们回馈了多少?当使用MyFree 返回相邻的块时,如何将它们重新组合成更大的块?

【讨论】:

    【解决方案2】:

    我认为你真正的问题是堆栈是如何工作的。

    堆栈是程序启动时分配的一大块内存。有一个指向栈顶的指针。这个名字是暗示性的:想想一堆杂志。

    当一个函数被调用时,参数被放在栈顶。然后,函数本身将其局部变量放在上面。当函数退出时,堆栈指针会简单地移回调用函数之前的位置。这将释放函数使用的所有局部变量和输入参数。

    堆管理器与这块内存无关。欺骗free 将一些堆栈放入堆管理器的内存中将对您的程序造成严重破坏。当您调用其他函数时,内存可能会再次使用,如果您malloc 内存,则可能会同时使用内存,这充其量会导致数据损坏,最坏的情况是堆栈损坏(读取崩溃)。

    【讨论】:

      【解决方案3】:

      当您谈到在堆栈上分配内存时,您必须了解在大多数实现中,堆栈是在一个块中分配的——变量不是单独或单独分配的。

                           +-+ +--------------------------------------------------+
                           |   | Stack frame data section; local variables and    |
                           |   |                                                  |
                           |   | function arguments in order determined by the    |
                           |   |                                                  |
                           |   | calling convention of the target platform        |
      Stack frame for      |   |                                                  |
      function call;   +---+   | (size is implementation dependent)               |
      block allocated      |   |                                                  |
                           |   |                                                  |
                           |   +--------------------------------------------------+
                           |   |Instruction pointer (return address)              |
                           |   +--------------------------------------------------+
                           |   |Space for return value (if not in a CPU register) |
                           +-+ +--------------------------------------------------+
                               |                                                  |
                               |                                                  |
                               |                                                  |
                               |     (stack frame of previously called function)  |
                               |                                                  |
                               |                                                  |
                               +--------------------------------------------------+
      

      每个函数调用都分配有自己的堆栈帧,具有保存返回值(如果需要)、返回地址的指令指针以及所有局部变量和函数参数所需的大小。因此,虽然分配了堆栈帧的内存,但并未针对任何单独的变量进行分配——仅针对各个大小的总和。

      【讨论】:

        猜你喜欢
        • 2013-11-22
        • 1970-01-01
        • 2016-07-25
        • 1970-01-01
        • 1970-01-01
        • 2012-02-20
        • 2011-12-11
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多