【问题标题】:Deallocating locally defined variables in C在 C 中释放本地定义的变量
【发布时间】:2019-10-11 16:01:33
【问题描述】:

假设我们有以下代码:

void foo() {
  char buffer[100];
}

C 中是否有(最好是可移植的)方法从运行时堆栈中释放 buffer(类似于 add esp, 100 在程序集中),在 foo 之前() 返回?

【问题讨论】:

  • 执行退出函数时会“从运行时栈中释放”。
  • @goodvibration 添加了说明。
  • 可移植的方式是代码对释放buffer没有任何作用。就程序而言,当函数返回时,buffer 将不复存在。实现(即编译器)负责确保任何可能需要的清理,并且 C 代码没有标准/可移植的方式来影响(甚至监控)——唯一的方式是依赖于机器的代码(例如汇编)根据定义,它是不可移植的。如果你需要提供buffer被释放的证据,那么你需要进行与你的编译器相关的验证和验证,而不是你的代码
  • 这听起来像XY problem。你真正想做的是什么?
  • 很公平。然后,您可以在函数中对数组和变量进行子范围划分。或者在这种情况下只使用堆分配。在一些需要快速分配和缓冲区重用的有限情况下,我构建了一个简单的缓冲池堆栈。

标签: c memory-management local-variables stack-pointer static-allocation


【解决方案1】:

没有。在 C 中你能做的最好的事情就是使用作用域:

void foo()
{
    {
        char buffer[100];
    }
}

并依靠编译器来考虑在内部范围退出后再次可用的 buffer 的 100 个字节。不幸的是,标准不能保证这一点,您需要依赖编译器。例如,考虑以下程序在堆栈空间为 8192KB (ulimit -s) 的典型 Linux 机器上:

#include <stdio.h>

int main(void)
{
    {
        char buffer1[8192 * 800] = { 0 };
        ((char volatile *)buffer1)[0] = buffer1[0];
        printf("%p\n", buffer1);
    }

    {
        char buffer2[8192 * 800] = { 0 };
        ((char volatile *)buffer2)[0] = buffer2[0];
        printf("%p\n", buffer2);
    }

    return 0;
}

(奇怪的转换是为了防止缓冲区变量被优化出来。)

在不使用优化构建时,程序会溢出某些编译器上的可用堆栈空间。例如clang -O0 会崩溃,但clang -O1 会为buffer2 重用buffer1 内存,并且两个地址将相同。换句话说,buffer1 在某种意义上在作用域退出时被“释放”了。

另一方面,即使在 -O0,GCC 也会进行此优化。

【讨论】:

  • 我会接受这个答案,因为这似乎是最好的选择。谢谢。
【解决方案2】:

它是自动变量,您不必做任何事情,因为它会在离开它的范围时重新分配。从函数返回时的情况

如果您想缩小范围,只需将其放在另一个范围内。

foo()
{
      for(... .)
      {
              // if defined here it will be only in the for loop  scope
       }

       {
           // if defined here it will be only in this internal scope
        }

}

请记住,它需要允许它的 C 标准。

【讨论】:

    【解决方案3】:

    给定:

    void foo(void) {
      char buffer[100];
    }
    

    对象buffer生命周期从执行到达开头{(进入函数时)开始,并在执行离开块时结束,到达关闭}(或通过其他方式,例如 gotobreakreturn 语句)。

    除了离开块之外,没有其他方法可以结束buffer 的生命周期。如果您希望能够释放它,则必须以其他方式分配它。例如,如果您通过调用malloc() 分配一个对象,您可以(并且几乎必须)通过调用free 来释放它:

    void foo(void) {
        char *buffer_ptr = malloc(100);
        if (buffer_ptr == NULL) /* error handling code here */
        /* ... */
        if (we_no_longer_need_the_buffer) {
            free(buffer_ptr);
        }
        /* now buffer_ptr is a dangling pointer */
    }
    

    另一种方法是通过在嵌套块中定义buffer 来限制它的生命周期:

    void foo(void) {
        /* ... */
        {
            char buffer[100];
            /* ... */
        }
        /* buffer's lifetime has ended */
    }
    

    但仅仅因为buffer 的生命周期在控制离开内部块时结束,这并不能保证它在物理上被释放。在抽象机中,buffer离开内部块后不再存在,但生成的代码可能会因为更方便而将其留在堆栈中。

    如果你想控制分配和释放,你需要使用mallocfree

    【讨论】:

    • 我用 gcc 进行了实验,我能够通过在循环中使用动态大小的数组来“强制它的手”。编译器必须保证每次循环时至少与声明的大小一样多。所以第一次,我使用了全尺寸,然后第二次我把尺寸降低到 1。编译器不知道第二个尺寸是更大还是更小,将数组的大小调整为 1。我意识到没有保证堆栈会缩小,但在实践中,它似乎很可能会缩小(并且它使用 gcc)。
    • VLA(在 C90 中不存在,在 C99 中是必需的,在 C11 中是可选的)有一些不同的规则。 VLA 的生命周期从其声明点开始,而不是在进入封闭块时开始。您是如何验证内存实际上已被释放的?
    • 我刚刚从循环中调用了一个函数,该函数打印了一个局部变量的地址。所以我可以看到调用者堆栈帧的大小发生了变化。
    【解决方案4】:

    因为char buffer[100]; 被声明为void foo() 的函数堆栈的本地,所以当void foo() 返回时,buffer 的存储被释放以供重用,并且在此之后不再有效访问。

    因此,您无需或可以做任何事情来“解除分配缓冲区”,因为buffer 是一个具有自动存储持续时间的数组,例如见:C11 Standard - 6.2.4 Storage durations of objects (p5)

    【讨论】:

    • 请看我对 selbie 的评论。
    • @D.Fonkaz - 是的,很高兴指出堆栈空间是有限的(Windows 上为 1M,Linux 上为 2-4M)
    猜你喜欢
    • 2014-11-25
    • 1970-01-01
    • 2011-01-26
    • 2017-12-02
    • 1970-01-01
    • 2021-09-27
    • 1970-01-01
    • 2015-07-07
    • 1970-01-01
    相关资源
    最近更新 更多