C++ 若在声明之前返回,是否在堆栈上分配了变量?

C++ 若在声明之前返回,是否在堆栈上分配了变量?,c++,C++,假设我有一个函数,其粗略结构如下所示: int aRecursiveFunction(const SomeLargeStructure *a, int x) { if (end recursion) return 0; // ... if (something that is mostly true) return aRecursiveFunction(a, x+1)+1; // ... SomeLargeStructure

假设我有一个函数,其粗略结构如下所示:

int aRecursiveFunction(const SomeLargeStructure *a, int x) {
    if (end recursion)
        return 0;
    // ...
    if (something that is mostly true)
        return aRecursiveFunction(a, x+1)+1;
    // ...
    SomeLargeStructure copy = *a;
    alter(&copy);
    return aRecursiveFunction(&copy, x);
}
我需要知道的是,出于性能原因,在函数在此点之前结束的90%情况下,是否会在堆栈上创建
copy
(这是一个大型结构)的空间。还是说这根本不重要?它依赖于编译器吗?将此部分作为另一个功能分开是否更好

谢谢

EDIT:为了澄清,
sizeof(somelargestrestructure)
大约是500,它只有基本类型和数组(没有特殊的构造函数或赋值运算符等)


编辑2:好的,结论似乎是堆栈空间可能每次都会被分配,但不会影响性能。堆栈溢出在这里不是问题,因此案例已结束。

在大多数平台上,可能需要的最大堆栈空间是在函数项上分配的。但通常分配堆栈空间只是加法,所以分配的空间量对性能没有影响

不过,你的问题对我来说未成熟。这不是一个算法问题,也没有衡量性能的问题。那么,你为什么还要考虑微优化呢?

有些编译器可能是你的。当它们进行优化时,调用堆栈框架被重用,因此不会增长(因此尾部调用被优化为“带参数的goto”)。IIRC,最近,您遇到了尾部调用未优化的情况(但我可能错了,GCC正在这方面取得进展)

您可能希望编译成汇编程序代码,例如使用
g++-fverbose asm-S-Wall-O
并查看生成的汇编程序代码

<>你当然不应该期望每个C++编译器都能像你一样优化尾部调用。 如果它对您如此重要,并且如果可移植性不是一个问题,那么您可以先使用一个新的操作符,然后再使用一个新的操作符

  SomeLargeStructure* copyptr = 
    new (alloca(sizeof(SomeLargeStructure))) SomeLargeStructure;
  *copyptr = *a;
  alter(copyptr);
  return aRecursiveFunction(copyptr, x);
如果您有一个复制构造函数,请使用
new(alloca(sizeof(SomeLargeStructure)))SomeLargeStructure(*a)取而代之

顺便说一句,对于现代台式机或服务器(堆栈空间通常略大于1兆字节)上半KB大小的调用帧,我不确定这样做是否值得,除非您认为您的递归非常深入(例如,超过1000级的递归)


最近,您可能会对诸如或之类的编译器选项感兴趣。它实际上可以归结为编译器在分析运行时如何执行代码的过程中可以获得多少信息。在最坏的情况下(即编译器无法推断数据是否会被执行),编译器必须为每个函数调用分配堆栈空间:

struct SomeLargeStructure {
    double arr[20];
};

int aRecursiveFunction(const SomeLargeStructure *a, int x) {
    int val;
    cin >> val; // User input isn't foresee-able
    if (val == 29)
        return 0;
    // ...

    if (val)
        return aRecursiveFunction(a, x + 1) + 1;
    // ...
    SomeLargeStructure copy = *a;
    if (val == 22)
      copy.arr[0] = 2.0; // whatever
    return aRecursiveFunction(&copy, x);
}

int main(int argc, char *argv[]) {
    SomeLargeStructure obj;
    aRecursiveFunction(&obj, 2);
}
以上要求在每次调用时进行完整堆栈分配(
-O3
):

同样,在人类推理可能认为“这绝对不需要”的情况下,编译器仍然可以选择分配堆栈空间

在这种情况下,如果没有看到完整的代码和/或研究编译器的行为,就不可能正确地回答这个问题。您应该编译代码,自己查看生成和优化的程序集。仅仅分配堆栈空间通常不是性能问题(除非堆栈溢出)


但有一点建议:这通常是一个过早的优化,正如其他人所指出的,您不应该担心这些低级问题,而应该专注于您的算法和数据的使用方式,特别是如果堆栈空间消耗没有证明/似乎没有问题,分析阶段将提示您最需要优化的红色区域。

我不确定,但我猜编译器可以随心所欲。即使假设有条件的堆栈分配是可能的,如果它优化了速度,分配总是可能的选择。您认为分配50000字节的堆栈空间比分配48字节的堆栈空间需要更长的时间吗?在大多数平台上,分配堆栈空间只是一种加法。我不知道,可能不知道,但这就是我为什么要问的原因。@DavidSchwartz这不会花费任何额外的时间,但可能会影响局部性。(在这种情况下,我对此表示怀疑,但一般来说。)如果我们将“性能”解释为“速度”,这是正确的。实际的堆栈大小可能仍然很重要,例如,对于堆栈溢出的可能性有多大的问题。@jogojapan我不确定这里的“性能”是什么意思。我想他可能会调用一个占用大量堆栈空间的函数,如果且仅当他的大结构没有被分配时,但这似乎是难以置信的不可能的。我不明白为什么现在还为时过早。实际的函数要长得多、复杂得多,所以我只是发布了它的大致结构(如我所说),来询问这个特定的部分。一段时间以来,我一直在努力提高速度。这也会影响性能:记住缓存,这对堆栈上的内容非常重要!对我来说,调用具有1KB堆栈帧的函数可能比调用较小的函数慢10亿倍,这似乎是合乎逻辑的。
aRecursiveFunction(SomeLargeStructure const*, int):
    pushq   %r15
    pushq   %r14
    pushq   %rbx
    subq    $176, %rsp // stack alloc
    movl    %esi, %ebx
    movq    %rdi, %r14
    leaq    172(%rsp), %rsi