当前位置: 首页 > 面试题库 >

使用局部变量的编译器,无需调整RSP

穆招
2023-03-14
问题内容

有问题的编译器:了解小型程序生成的汇编代码,编译器使用两个局部变量,而无需调整堆栈指针。

不为使用局部变量而对 RSP 进行调整似乎 并不安全 ,因此 中断
似乎使编译器依赖于硬件自动切换到系统堆栈。否则,出现的第一个中断会将指令指针压入堆栈,并覆盖局部变量。

该问题的代码是:

#include <stdio.h>

int main()
{
    for(int i=0;i<10;i++){
        int k=0;
    }
}

该编译器生成的汇编代码为:

00000000004004d6 <main>:
  4004d6:       55                      push   rbp
  4004d7:       48 89 e5                mov    rbp,rsp
  4004da:       c7 45 f8 00 00 00 00    mov    DWORD PTR [rbp-0x8],0x0
  4004e1:       eb 0b                   jmp    4004ee <main+0x18>
  4004e3:       c7 45 fc 00 00 00 00    mov    DWORD PTR [rbp-0x4],0x0
  4004ea:       83 45 f8 01             add    DWORD PTR [rbp-0x8],0x1
  4004ee:       83 7d f8 09             cmp    DWORD PTR [rbp-0x8],0x9
  4004f2:       7e ef                   jle    4004e3 <main+0xd>
  4004f4:       b8 00 00 00 00          mov    eax,0x0
  4004f9:       5d                      pop    rbp
  4004fa:       c3                      ret

局部变量是i[rbp-0x8]k[rbp-0x4]

任何人都可以在这个中断问题上大放异彩吗?硬件确实切换到系统堆栈吗?怎么样?我理解错了吗?


问题答案:

这就是x86-64 ABI的所谓“红色区域”
。维基百科的摘要:

在计算中,红色区域是函数堆栈帧中超出当前堆栈指针的固定大小区域,该区域未被该函数保留。被调用方函数可以使用红色区域来存储局部变量,
而无需修改堆栈指针 。中断/异常/信号处理程序不能修改此内存区域。System V使用的x86-64
ABI要求一个128字节的红色区域,该区域直接从堆栈指针的当前值开始。

在64位Linux用户代码中,只要使用的字节数不超过128个即可。这是叶函数(即不调用其他函数的函数)最主要使用的优化

如果使用该选项将示例程序编译为带有 GCC (或兼容的编译器)的64位Linux程序,-mno-red-zone则会生成如下所示的代码:

main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16;     <<============  Observe RSP is now being adjusted.
        mov     DWORD PTR [rbp-4], 0
.L3:
        cmp     DWORD PTR [rbp-4], 9
        jg      .L2
        mov     DWORD PTR [rbp-8], 0
        add     DWORD PTR [rbp-4], 1
        jmp     .L3
.L2:
        mov     eax, 0
        leave
        ret

可以在此godbolt.org链接上观察到此代码的生成。

对于32位Linux用户程序,不调整堆栈指针将是一件坏事。如果要将问题中的代码编译为32位代码(使用-m32选项),main则会显示以下代码:

main:
        push    ebp
        mov     ebp, esp
        sub     esp, 16;     <<============  Observe ESP is being adjusted.
        mov     DWORD PTR [ebp-4], 0
.L3:
        cmp     DWORD PTR [ebp-4], 9
        jg      .L2
        mov     DWORD PTR [ebp-8], 0
        add     DWORD PTR [ebp-4], 1
        jmp     .L3
.L2:
        mov     eax, 0
        leave
        ret

您可以在此gotbolt.org链接中观察到此代码的生成。



 类似资料:
  • 问题内容: 我正在使用handlebars.js,我想开始对所有内容进行预编译,但是似乎找不到找到预编译部分的方法。我的大部分模板实际上都是局部的。我尝试将它们像常规模板一样对待,但是将它们作为部分模板调用是行不通的。 有什么方法可以预编译局部函数,或者从另一个模板中调用一个模板吗? 问题答案: 我找到了一个更好的方法:将所有的部分代码预编译为模板,然后在代码中使用它们之前,添加以下行: 改进之处

  • 问题内容: 所以这很奇怪,我知道代码本身是没有用的,但是我想知道为什么会收到错误: 我正在写一些代码,我已经写了这个: 当时没有考虑可变范围,显然这是没有用的,因为无论如何我都无法使用过去。我没有得到的是为什么我得到了我的错误: 现在,如果我将其修改为: 它将编译正常。我给人的印象是,如果大括号下有1行是可选的……显然,还有其他考虑,或者两者都会编译或失败。 有人可以向我解释一下,还是可以指向一个

  • 问题内容: 在Objective-C中,我在构建设置->代码中使用的其他C标志中设置了一堆编译器标志。例如: 标志=> -DPortNumber = 1 在代码中,我能够通过 这在Swift中不起作用,而且我找不到答案。 问题答案: C编译器的标志定义了预处理器宏。Swift中没有预处理器宏。因此,如果您要执行以下操作: …你不能。Swift旨在使源代码在编译之前在语法上完整。如果您可以在构建时关

  • 我最近一直在尝试重新熟悉多线程,并找到了这篇论文。其中一个例子说在使用类似这样的代码时要小心: 声明: 是作者写错了,还是我漏掉了什么?

  • 上面所有的变量都有标题中的警告。你能告诉我为什么会这样吗?

  • 问题内容: 在lambda中,局部变量需要是最终变量,而实例变量则不需要。为什么这样? 问题答案: 字段和局部变量之间的根本区别在于,当JVM创建lambda实例时,将复制局部变量。另一方面,字段可以自由更改,因为对它们的更改也将传播到外部类实例(它们的范围是整个外部类,如Boris所指出的)。 考虑到匿名类,闭包和Labmdas的最简单方法是从可变范围的角度来看。想象一个为传递给闭包的所有局部变