【问题标题】:How do nested scopes affect stack depth?嵌套范围如何影响堆栈深度?
【发布时间】:2021-05-02 15:35:45
【问题描述】:

我尝试使用 MSVC 将以下 c 代码编译成带有 (CL TestFile.c /Fa /Ot) 和没有优化 (CL TestFile.c /Fa) 的程序集,结果是它们产生了相同的堆栈深度。

当编译器知道最多使用 16 个字节时,为什么要为 3 个变量 x、y 和 z 中的每一个使用 8 个字节?而不是y$1 = 4z$2 = 8 可以不使用y$1 = 4z$2 = 4 所以yz 在堆栈上使用相同的内存没有任何问题?

int main() {
  int x = 123;
  if (x == 123) {
    int y = 321;
  }
  else {
    int z = 234;
  }
}
; Parts of the assembly code
x$ = 0
y$1 = 4
z$2 = 8
main PROC
$LN5:
  sub rsp, 24
; And so on...

【问题讨论】:

  • 我现在尝试编译使用:CL TestFile.c /Fa /Ot 所以它会优化,但输出是一样的。编译器似乎没有对此进行优化。
  • 我希望这个程序能够编译成一个完全优化的返回指令,因为它什么都不做。
  • @EugeneSh。不稳定的,它们不应该被优化出来
  • @0___________ Volatile 可能还有其他一些影响我猜...因为这个问题是关于某些优化的

标签: c assembly compiler-construction compiler-optimization


【解决方案1】:

嵌套范围不影响堆栈深度。根据 C 标准,嵌套范围会影响标识符的可见性,并且不会对 C 实现如何使用堆栈(如果有的话)施加任何要求。 C 标准允许 C 编译器生成任何具有相同可观察行为的代码。

对于问题中显示的程序,唯一可观察到的行为是以成功状态退出,因此一个好的编译器应该在优化时生成一个最小程序。例如GCC 10.2 for x86-64 generates just an xor and a ret:

main:
        xor     eax, eax
        ret

Clang 11.0.1 也是如此。如果 MSVC 没有,那就是它的缺陷。 (但是,/Os/Ot 开关可能不要求优化或不要求太多优化;当与其他优化开关结合使用时,它们可能只是表达对速度或时间的偏好。)

此外,一个好的编译器应该对对象的使用进行生命周期分析,构建一个图来表示节点在代码中的位置,并用值的创建或使用进行标记,而有向边是潜在的程序控制流(或一些等效的表示的源代码)。然后应生成汇编程序(或中间代码)以实现图所需的语义。如果两组源代码具有等价的图,编译器应该为它们生成等价的程序集(或中间代码)(达到处理复杂图的合理能力),无论是否使用嵌套范围内的定义。

【讨论】:

  • 为什么编译器不尝试在我的例子中减少堆栈深度?它不能减少大型函数的执行时间吗?
  • @JasperNielsen:MSVC 确实做了这个优化:godbolt.org/z/f9M8Kj
  • @PeterCordes:/Os/Ot 是否不要求任何优化,而只是要求,如果需要优化,分别首选速度或时间?这可以解释为什么代码在 /Os/Ot 上不好,而在 /O2 上不好。
  • @JasperNielsen:实际上,即使在if 的每个分支中添加foo(&y)foo(&z) 调用,并使x 不是编译时常量godbolt.org/z/sGdfKz,我们确实看到 MSVC 19.14 对 yz 使用相同的堆栈槽(注意 z$1=48y$2=48 RSP 的汇编时常量偏移量),正如这个答案的最后一段所暗示的那样,一个好的编译器应该。似乎只是将它们组合成一个变量,其值由分支决定。
  • @EricPostpischil: IDK, -Ot 与无选项(调试模式)相同。但奇怪的是,对于 WINE 下的 Godbolt 的 MSVC,该 asm 不会使 RBP 成为帧指针,所以它可能不是完整的调试模式?听起来,如果 OP 想要使用标准 -O2-Ox 以外的选项,他们需要查看 MSVC 手册。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-13
  • 2012-01-09
  • 2017-06-01
  • 2020-09-30
  • 2014-10-16
  • 2012-06-02
相关资源
最近更新 更多