在 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??
我以为规则是在块结束时移动堆栈指针以再次使用堆栈内存。
这个原则是优化的领域吗?
调试版本通常是故意未优化的。可能是每个变量,无论作用域如何,在堆栈中都有自己的位置,这样当您的代码崩溃时,它可以向您显示每个变量的状态。如果他们都共享一个地址,这是不可能的。
结果取决于您使用的编译器。
我试过在线编译器。我得到了相同的地址。
我试过这个在线编译器。
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 的内存地址不同。
我测试了以下代码:
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??
我以为规则是在块结束时移动堆栈指针以再次使用堆栈内存。
这个原则是优化的领域吗?
调试版本通常是故意未优化的。可能是每个变量,无论作用域如何,在堆栈中都有自己的位置,这样当您的代码崩溃时,它可以向您显示每个变量的状态。如果他们都共享一个地址,这是不可能的。
结果取决于您使用的编译器。
我试过在线编译器。我得到了相同的地址。
我试过这个在线编译器。
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 的内存地址不同。