【问题标题】:Freeing alloca-allocated memory释放 alloca 分配的内存
【发布时间】:2010-09-21 22:23:50
【问题描述】:

是否可以在当前函数退出之前显式释放 C 的 alloca() 分配的内存?如果有,怎么做?

【问题讨论】:

  • 你能解释一下你的动机吗?为什么要在返回之前释放分配的空间?

标签: optimization memory-management


【解决方案1】:

来自http://www.gnu.org/software/libc/manual/html_mono/libc.html#Variable-Size-Automatic

使用alloca 分配块是显式操作;您可以根据需要分配任意数量的块,并在运行时计算大小。但是,当您退出调用 alloca 的函数时,所有块都会被释放,就像它们是在该函数中声明的自动变量一样。 无法明确释放空间。

【讨论】:

    【解决方案2】:

    这是可能的,但没有预先编写的函数可以做到这一点。您必须深入研究编译器的 alloca() 实现以弄清楚它在做什么,然后编写自己的 freea()。由于每个编译器执行 alloca() 的方式不同,因此您必须为每个编译器重写 freea()。

    但我很难相信这值得麻烦。如果您需要显式释放它,只需使用 malloc/free - 这些功能通常都经过大量优化。充分利用它们。

    【讨论】:

    • 一个可移植的实现是“void freea(void *p) {} // Just fake it”。
    • 嗯,没有。您无法移动堆栈指针而不会损坏,并且您无法重新定位堆栈上的项目,因为它们的地址可能存储在其他地方。 free/malloc 工作的原因是它们可以完全控制自己的堆空间。
    • 我已经实现了 alloca(),是的,你可以做一个 freea()。您不会像 alloca() 那样重新定位项目 - 获取地址的本地人必须在分配空间之前。无论如何,您都不应该将悬空指针留在已释放的空间中,就像您可以为已释放的空间一样。
    • 如果分配很大,一些实现会进入堆。我知道微软的。
    • 我已经实现了 alloca(),是的,你可以做一个 freea() - 哦,真的吗? a=alloca(asize);b=alloca(bsize); 你能描述一下freea(a); 电话会做什么吗?
    【解决方案3】:

    您正在使用 alloca() 在堆栈上进行分配;如果之后发生了其他事情(并且如果不将所有内容都写入汇编中就无法控制它),则不能只是将堆栈缩小。因此,除非您离开函数的堆栈框架,否则这是不可能的。

    这也是为什么如果您溢出分配的缓冲区,您真的会搞砸事情。你可以开始覆盖你的函数返回的代码地址,导致它跳转到其他地方,各种可怕的东西。小心!

    Malloc 在堆上工作,因此它的功能更加灵活。

    【讨论】:

      【解决方案4】:

      使用 C99,您可以使用 Variable Length Array 实现相同的目的。只需在新范围内声明 VLA;作用域退出时会自动释放。

      例如:

      int some_function(int n) {
          // n has the desired length of the array
          ...
          { // new scope
              int arr[n]; // instead of int *arr = alloca(n*sizeof(int));
              // do stuff with array
          }
          // function continues with arr deallocated
          ...
      }
      

      【讨论】:

        【解决方案5】:

        这对于延续传递风格 (CPS) 很有用,而不是 realloca。

        您可以调用一个函数来分配和操作堆栈顶部的字符串,然后将堆栈缩小到字符串的长度并调用下一个函数。

        为什么不能有 freea() 没有概念上的原因,这将是一个 nop,除了堆栈上的最顶层条目。

        【讨论】:

          【解决方案6】:

          不,因为它与局部变量一起分配在堆栈上。如果您想要可以显式释放的内存,请使用动态内存分配函数之一。

          没有混合允许您显式释放并且在函数退出时也自动释放它,至少在标准中没有。

          【讨论】:

            【解决方案7】:

            是的,但这取决于 alloca() 的实现。 alloca() 的一个合理简单的实现是通过调整堆栈指针将新分配的块放在栈顶。因此,要释放这块内存,我们只需要进行一次分配(但您需要研究 alloca() 的真正实现),让我们通过以下 non-可移植的代码例如:

            #include <stdio.h>
            #include <alloca.h>
            
            int main()
            {
              unsigned long p0, p1, p2;
              p0=(unsigned long)alloca(0);
              p1=(unsigned long)alloca((size_t) 0x1000);
              p2=(unsigned long)alloca((size_t)-0x1000);
              printf( "p0=%lX, p1=%lX, p2=%lX\n", p0, p1, p2 );
              return 0;
            }
            

            在带有 clang 2.9 的旧 x64 机器上,示例输出为:

            p0=7FFF2C75B89F, p1=7FFF2C75A89F, p2=7FFF2C75B89F
            

            所以我们知道实现验证参数 -0x1000,否则无符号值将是一个非常大的整数。堆栈指针原本是 0x...B89F;由于此堆栈向上增长 alloca(0x1000) 因此将堆栈指针 up 更改为 (0x...B89F - 0x1000) = 0x...A89F。在负分配 (0xA89F - (-0x1000)) 之后,堆栈指针回到 0x...B89F。

            但是,对于 gcc 4.8.3,示例输出是:

            p0=7FFFA3E27A90, p1=7FFFA3E26A80, p2=7FFFA3E27A70
            

            在 /usr/include/alloca.h 我们发现:

            #ifdef  __GNUC__
            # define alloca(size)   __builtin_alloca (size)
            #endif /* GCC.  */
            

            所以我们知道 gcc 4.8.3 提供的内置 alloca 函数做了类似的事情,只是它分配了额外的 0x10 字节作为安全边际。在进行负分配时,它仍然假设它向上增长,因此尝试保留 0x10 额外字节(- 0x10),因此 p2= 0x...6A80 - (-0x1000) - 0x10 = 0x...7A70。所以,要格外小心。

            【讨论】:

              【解决方案8】:

              您不需要编写任何自定义的freea(...) 类型的函数,也不需要使用 VLA。 在 C 和 C++ 中可以轻松释放堆栈上分配的内存(C++ 不支持 VLA)。 alloca(...) 在堆栈上分配,对吗?这意味着内存将在超出范围时被释放......所以只需使用范围!

              #include <alloca.h>
              
              int main()
              {
                  {
                      void* ptr = alloca(1024);
              
                      // do your stuff
              
                  } // memory is deallocated here
                  
                  return 0;
              }
              

              从 Godbolt 在线编译器中可以看出,程序集(没有优化)做了正确的事情: https://godbolt.org/z/Gn5YMa

              【讨论】:

                猜你喜欢
                • 2011-12-09
                • 1970-01-01
                • 2011-05-13
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-02-01
                相关资源
                最近更新 更多