【问题标题】:Is this use of alloca() valid?使用 alloca() 有效吗?
【发布时间】:2023-03-18 21:12:01
【问题描述】:

在使用标准向量保存我的国际象棋引擎的移动列表后,我意识到因为国际象棋的平均因子为 35(即从典型位置开始的 35 次合法移动),向量的大小调整了很多,因此对移动生成器的性能产生负面影响。解决这个问题的一种方法(我今天才意识到)是为向量保留最小容量。然而,使用 alloca() 的可能性引起了我的注意。这可能是一个非常简单的问题,但是关于 alloca() 的文档非常少,关于如何使用它的示例非常少。

所以Allocation of variable-sized class 的回答提到堆栈分配无法调整大小。然而,以下内容是否有效?

    struct MoveList
    {
       MoveList(): capacity(10)
       {
          moves = (Move*) alloca(sizeof(Move) * 10);
       }
       void resize()
       {
          capacity *= 2;
          moves = (Move*) alloca(sizeof(Move) * capacity );
       }
       void push_back();
       Move* moves;
       int size;
       int capacity;
    }

具体来说,如果说第一次使用 alloca() 的容量为 10 是不够的,那么再次调用 alloca() 以分配更多内存在语法上是否有效(并且正确)? 这种方法会提供更好的性能(与使用 reserve() 的 std 向量相比),还是只会增加堆栈溢出的机会? 我的 Move 结构需要大约 28 字节的内存,我怀疑引擎将递归搜索(使用 alpha-beta)到最多 7 或 8 层,以便从堆栈中使用一些 28 * 35 * 8 ~ 8kb max。我在某处读到,通常堆栈的限制为 1Mb,所以这应该不会太多吧?

编辑:感谢下面的答案,我现在意识到我最初对 alloca() 所做的理解是错误的。但是,我仍然想知道是否可以通过以下方式使用 alloca():

    int main()
    {
        int* arr = (int) alloca(sizeof(int));
        arr = alloca(sizeof(int) * 2 ));//is this 'resizing' valid?
    }

【问题讨论】:

  • 为什么不直接使用arrayvector?例如Move* moves = new Move[10];
  • Why is alloca not considered good practice,加上 alloca 不会调用 C++ 类的构造函数。
  • 啊,如果可能的话,我想要一个快速移动生成器:) 您的评论实际上是我的问题的一部分。使用 alloca() 会比使用 reserve() 的 std 向量提供更好的性能吗?
  • "Resizing" 在您使用alloca 代表ints 的示例中是有效的,但它没有调整大小。您在堆栈上分配了两次 - 第一次是一个 int 的区域,第二次是两个 ints 的区域。所以实际上你为三个ints 分配了内存。
  • @UserUnspecified 与此处的malloc 没有区别。最后一次调用后它指向[2],所以有两个元素。以前指向 [1] 的指针丢失了,因为它的地址在 arr 中,现在被替换了 - 但它仍然在堆栈上,正在消耗内存。与 malloc 的唯一区别是在返回时会被丢弃,而 malloced 的值必须手动释放,否则会导致内存泄漏。

标签: c++ alloca


【解决方案1】:

函数alloca在堆栈上分配内存,一旦调用alloca的函数返回,内存就不再可用。这意味着一旦MoveList 构造函数或resize 函数返回,内存就不再可用。您认为您可以在 MoveList 对象的生命周期内以某种方式使用此内存的假设是错误的。

您最好的选择是使用std::vector 并保留。

【讨论】:

  • 感谢您提供的非常丰富的答案!但是,语法有效吗?实际上,我应该能够在直接创建和使用 MoveList 的函数中调用 alloca()。
  • 语法是有效的,但是如果你尝试以这种方式使用alloca,你会打开潘多拉的盒子。对于初学者,不会调用 Move 构造函数。首先尝试使用标准技术获得工作版本,并且只有在效率不够高时才尝试使用技巧。从技巧开始就是自找麻烦。
  • 过早的优化是万恶之源,嗯?好吧,我会坚持使用 std::vector 并保留。
  • @UserUnspecified 构造函数问题可以很容易地解决,但问题是 - 除了返回之外,没有办法释放 alloca'ted 内存。因此,如果您用新的allocated 替换指针,旧指针会在堆栈上丢失,但仍会使用内存。许多后续调整大小将严重影响堆栈​​,从而提供很好的溢出机会。变长数组没有这个问题,因为它们是基于作用域的,而不是基于函数的;但并不是每个编译器都支持 VLA。
  • @keltar:是的,可变长度数组是我想到的第一个解决方案,但我认为我当前的编译器不支持 c99 :( 你提出的关于堆栈溢出的观点很好顺便提一下我的另一个问题
【解决方案2】:

您似乎不明白非标准 alloca() 表达式的实际作用。它在调用函数的堆栈帧中分配内存。在您的情况下,这意味着分配空间的生命周期(在这种情况下分配给 moves 成员)是构造函数的生命周期:

   MoveList(): capacity(10)
   {
      moves = (Move*) alloca(sizeof(Move) * 10);
      ... moves is valid from this point

      // "moves" stops being valid at this point
   }

由于您的构造函数的其余部分是空的,这不是您想要的。 (另外,alloca() 具有阻止调用函数被内联的副作用——另一个意想不到的副作用。)换句话说,回答标题中的问题,alloca() 的这种使用是无效。

即使它以某种方式有效,因为alloca() 没有对应物来调整或释放分配的内存(由于它的工作方式,它也没有对应物),它对于该区域需要的任何情况都是非常不合适的调整大小 - 这正是您尝试使用它的方式。

std::vector 的大小调整容量通常已经考虑到指数级增长,因此无需添加您自己的容量。如果您不确定,请衡量性能并查看适合您的方法。也许对于您的情况,调用std::vector<T>::reserve() 以确保向量以乐观的大小开始就足够了,从而消除了重新分配的需要。或者使用std::deque,它从不重新分配元素(代价是访问速度稍慢)。

【讨论】:

  • Alloca 防止调用函数被内联?这是因为编译器拒绝内联函数还是因为程序员向编译器建议内联太危险了?
  • @UserUnspecified 编译器拒绝内联它,至少GCC does.
  • @UserUnspecified 如果你想一想,使用alloca 内联函数没有多大意义,这是关于高效的 stack 分配和按堆栈及时释放放松。想象一下在循环中调用一个使用alloca 的函数f()。内联对f() 的调用会很快导致堆栈溢出,因为alloca 会不断增长调用者的堆栈。使f() 保持外线可确保f() 中的alloca 调用占用的空间在每次从f() 退出时释放。
  • @user4815162342 是的,但仍不禁止内联。编译器通常不想这样做,因为 alloca 调用是相当重要的因素。但是至少在我的测试中,__attribute__ ((always_inline)) inline 发生了。因此,这只是编译器处理为“最好不要内联”的许多事情之一。
  • 为了明确我的观点 - 编译器 可以 内联函数并在“离开”函数后仍然重置其堆栈(根据范围),就像它对 VLA 所做的那样。不过,不知道是否有任何编译器真的这样做了。
猜你喜欢
  • 2013-04-24
  • 1970-01-01
  • 2014-07-12
  • 2016-10-13
  • 1970-01-01
  • 2011-03-28
  • 1970-01-01
  • 2011-01-20
  • 2011-03-30
相关资源
最近更新 更多