在 C++ 块作用域中,重新使用堆栈内存是优化区域吗?

In C++ block scope, is re-using stack memory the area of optimization?

我测试了以下代码:

void f1() {
  int x = 1;
  cout << "f1  : " << &x << endl;
}

void f2() {
  int x = 2;
  cout << "f2  : " << &x << endl;
}

void f3() {
  {
    int x = 3;
    cout << "f3_1: " << &x << endl;
  }
  {
    int x = 4;
    cout << "f3_2: " << &x << endl;
  }
}

int main() {
  f1();
  f2();
  f3();
}

release构建中,输出是...

f1  : 00FAF780
f2  : 00FAF780 
f3_1: 00FAF780
f3_2: <b>00FAF780</b>  <-- I expected

但在调试构建中,

f1  : 012FF908
f2  : 012FF908
f3_1: 012FF908
f3_2: <b>012FF8FC</b>  <-- what??

我以为规则是在块结束时移动堆栈指针以再次使用堆栈内存。
这个原则是优化的领域吗?

调试版本通常是故意未优化的。可能是每个变量,无论作用域如何,在堆栈中都有自己的位置,这样当您的代码崩溃时,它可以向您显示每个变量的状态。如果他们都共享一个地址,这是不可能的。

结果取决于您使用的编译器。

我试过在线编译器。我得到了相同的地址。

我试过这个在线编译器。

https://www.onlinegdb.com/online_c++_compiler

I thought the rule was to move the stack pointer to use the stack memory again when the block is ended.

说到优化,你不应该只考虑堆栈和堆,它们在做优化时肯定是一个重要的部分,但规范没有说明这一点,规范只谈论生命周期,存储期限和行为。堆栈和堆只是实现它们的一种方式。所以compiler/optimizer只要满足规范的要求,就可以为所欲为。

对于 POD 对象——没有任何构造或销毁的特殊行为——编译器可以完全优化这些对象(及其成员)并直接使用它的值(或其成员的值)。正如@tadman 在评论中已经说过的那样,询问地址可能会破坏许多可能的优化。当您明确告诉编译器您需要了解有关该对象的一些信息时。

它在很大程度上还取决于编译器、版本、编译器标志和编译的体系结构(arm、x64、haswell、sandy bridge 等)以及周围的代码。

因为编译器会假设生成的代码可能执行得最好。例如。允许管道和分支预测器做最好的工作。

如果你使用 printf 而不是 std::cout 输出可能是:

f1  : 0x7ffc7781f56c
f2  : 0x7ffc7781f56c
f3_1: 0x7ffc7781f54c
f3_2: 0x7ffc7781f54c

或者如果您将显示的所有代码放在一个函数中:

void f1() {
  {
    int x = 1;
    cout << "f1  : " << &x << endl;
  }
  {
    int x = 2;
    cout << "f2  : " << &x << endl;
  }
  {
    int x = 3;
    cout << "f3_1: " << &x << endl;
  }
  {
    int x = 4;
    cout << "f3_2: " << &x << endl;
  }
}

同一编译器的结果可能是:

f1  : 0x7ffc652aac34
f2  : 0x7ffc652aac34
f3_1: 0x7ffc652aac34
f3_2: 0x7ffc652aac34

所以栈(视觉表示)的概念,从生命周期的角度来说,是一种可视化正在发生的事情的方式,但绝不是在栈上的内存利用率(内存概念)方面实际发生了什么。编译器可以在堆栈(内存)上保留一定数量的内存,并以最佳顺序将使用过的值保存在其中。它通常与堆栈的视觉表示匹配,但不需要。

关闭优化后,编译器通常会在堆栈上为函数变量使用不同的位置。但这也不是保证。如果堆栈上的对象太大,即使在未优化的调试版本中,编译器也可能会停止这样做:


struct Foo {
   int x1;
   int x2;
   int x3;
   int x4;
   int x5;
   int x6;
   int x7;
   int x8;
   int x9;
   int x10;
};

void f1() {
  Foo x;
  cout << "f1  : " << &x << endl;
}

void f2() {
  Foo x;
  cout << "f2  : " << &x << endl;
}

void f3() {
  {
    Foo x;
    cout << "f3_1: " << &x << endl;
  }
  {
    Foo x;
    cout << "f3_2: " << &x << endl;
  }
}

int main() {
  f1();
  f2();
  f3();
}

gcc x86-64 的内存地址相同,但 clang x86-64 的内存地址不同。