简短的回答:Godbolt 添加了一个-C debuginfo=1
标志,强制优化器保持所有指令管理帧指针。当使用优化且没有调试信息进行编译时,Rust 也会删除这些指令。
这些指令在做什么?
这三个指令是函数 prologue 和 epilogue的一部分。特别是,他们在这里管理所谓的帧指针或基指针(rbp
在 x86_64 上)。注意:不要将基指针与堆栈指针混淆(rsp
在 x86_64 上)!基指针始终指向当前堆栈帧内:
┌──────────────────────┐
│ function arguments │
│ ... │
├──────────────────────┤
│ return address │
├──────────────────────┤
[rbp] ──> │ last rbp │
├──────────────────────┤
│ local variables │
│ ... │
└──────────────────────┘
基指针的有趣之处在于它指向堆栈中的一块内存,该内存存储rbp
. 这意味着我们可以很容易地找到前一个堆栈帧的基指针(来自调用“us”的函数的那个)。
更好的是:所有基指针都形成类似于链表的东西!我们可以很容易地跟随所有last rbp
的 s 向上走。这意味着在程序执行期间的每一点,我们都确切地知道哪些函数调用了哪些其他函数,这样我们最终会“在这里”。
让我们再次查看说明:
; We store the "old" rbp on the stack
push rbp
; We update rbp to hold the new value
mov rbp, rsp
; We undo what we've done: we remove the old rbp
; from the stack and store it in the rbp register
pop rbp
这些说明有什么用?
基指针及其“链表”属性对于调试和分析程序行为(例如分析)非常重要。如果没有基指针,生成堆栈跟踪和定位当前执行的函数会更加困难。
此外,管理帧指针通常不会减慢很多速度。
为什么优化器不删除它们,我该如何强制执行?
如果Godbolt 没有传递-C debuginfo=1
给编译器,它们通常会是。这指示编译器保留与帧指针处理相关的所有内容,因为我们需要它进行调试。请注意,调试本身并不需要帧指针——其他类型的调试信息通常就足够了。存储任何类型的调试信息时都会保留帧指针,因为在 Rust 程序中仍然存在一些与删除帧指针相关的小问题。这个 GitHub tracking issue正在讨论这个问题。
您可以通过自己添加标志-C debuginfo=0
来“撤消”它。这导致与 C++ 版本完全相同的输出:
example::double:
add dil, dil
mov eax, edi
ret
您还可以通过执行以下命令在本地对其进行测试:
$ rustc -O --crate-type=lib --emit asm -C "llvm-args=-x86-asm-syntax=intel" example.rs
如果您未显式打开调试信息,则使用优化 ( ) 进行编译-O
会自动删除处理。rbp