当前位置: 首页 > 知识库问答 >
问题:

编译器是否通常将寄存器用于其“预期”用途?

柴衡
2023-03-14

我一直在学习汇编,并且我读到四个主要的x86通用寄存器(eax,ebx,ecx和edx)都有一个预期或建议的目的。例如,eax 是累加器寄存器,ecx 用作循环的计数器,依此类推。大多数编译器是否尝试将寄存器用于建议的目的,或者他们是否忽略了寄存器“应该”用于什么,而只是将值分配给下一个可用的寄存器?

此外,在查看x64寄存器时,我注意到额外添加了8个通用寄存器,如果忽略rbp、rsp、rsi和rdi(因为它们具有非通用用途),则gp寄存器的总数将达到12个,如果包括它们,则为16个。在普通用户程序(即浏览器、文字处理器等,而不是需要大量寄存器的密码程序)中,在任何给定时间,通常使用多少寄存器?对于像Firefox这样的程序来说,一次使用所有12/16个普通寄存器是很常见的,还是因为没有足够的变量来填充它们,所以它们只使用一个子集?我将通过分解二进制文件来了解这一点,以了解一般情况,但我希望有比我更有知识的人提供答案。

此外,如果半通用寄存器(rsi、rdi、rsp和rbp)目前没有用于非通用应用,编译器通常会将它们用于通用用途吗?我很好奇,因为我看到这些寄存器被列为“通用”,但即使是我也能想到这些寄存器不能用于一般存储的实例(例如,您不会希望将变量存储到rbp和rsp,然后将值压入堆栈!).那么编译器会尽可能地利用这些寄存器吗?x86和x64编译之间有区别吗,因为x64处理器有更多可用的寄存器,所以没有必要将变量填充到任何可用的寄存器中?

共有2个答案

张唯
2023-03-14

最初(如在16位8086中),寄存器的功能比后来的x86处理器更加有限。只有BX、BP、SI和DI可用于寻址存储器,更常见的是使用CISC式的指令,用一条指令完成许多操作。

例如,LOOP 指令递减 CX,将其与零进行比较,如果 CX 仍为正,则跳转。如果你看一下为当前系统生成的代码,你不太可能看到这一点,但DEC和JNE。后者需要更多的代码空间,但允许您使用任何寄存器。

80386 和 32 位模式解除了寻址方面的大部分限制,允许将所有寄存器用作指针。此外,更复杂的指令已经过时了,我认为这与处理器本身的无序执行和其他优化技术的增加有关。

因此,在大多数情况下,几乎没有理由区别对待寄存器。当然,ESP/RSP 仍然是堆栈指针。

太叔涵亮
2023-03-14

所有GP寄存器都是通用的。
只有当执行特定的(通常是遗留的)指令时,它们才具有特殊意义。

例如,四元组<code>rsi,<code>rdi,<code>rbp,<code>rsp,只有后者有一个特殊的用途,这是由于<code>call</code>、<code<ret</code>、<code>push</code>等指令造成的。<br>如果不使用它们,即使是隐式使用(不太可能出现的情况),也可以将其用作累加器。

这个原则是通用的,编译器会利用它。

考虑这个人为的例子[1]

void maxArray(int* x, int* y, int*z, short* w) {
    for (int i = 0; i < 65536; i++)
    {
        int a = y[i]*z[i];
        int b = z[i]*z[i];
        int c = y[i]*x[i]-w[i];
        int d = w[i]+x[i]-y[i];
        int e = y[i+1]*w[i+2];
        int f = w[i]*w[i];

        x[i] = a*a-b+d; 
        y[i] = b-c*d/f+e;
        z[i] = (e+f)*2-4*a*d;
        w[i] = a*b-c*d+e*f;
    }
}

它由GCC编译到此列表中

maxArray(int*, int*, int*, short*):
        push    r13
        push    r12
        xor     r8d, r8d
        push    rbp
        push    rbx
        mov     r12, rdx
.L2:
        mov     edx, DWORD PTR [rsi+r8*2]    
        mov     ebp, DWORD PTR [r12+r8*2]
        movsx   r11d, WORD PTR [rcx+r8]
        mov     eax, DWORD PTR [rdi+r8*2]
        movsx   ebx, WORD PTR [rcx+4+r8]
        mov     r9d, edx
        mov     r13d, edx
        imul    r9d, ebp
        imul    r13d, eax
        lea     r10d, [rax+r11]
        imul    ebx, DWORD PTR [rsi+4+r8*2]
        mov     eax, r9d
        sub     r10d, edx
        imul    ebp, ebp
        sub     r13d, r11d
        imul    eax, r9d
        imul    r11d, r11d
        sub     eax, ebp
        add     eax, r10d
        mov     DWORD PTR [rdi+r8*2], eax
        mov     eax, r13d
        imul    eax, r10d
        cdq
        idiv    r11d
        mov     edx, ebp
        sub     edx, eax
        mov     eax, edx
        lea     edx, [0+r9*4]
        add     eax, ebx
        mov     DWORD PTR [rsi+r8*2], eax
        lea     eax, [rbx+r11]
        imul    r9d, ebp
        imul    r11d, ebx
        add     eax, eax
        imul    edx, r10d
        add     r9d, r11d
        imul    r10d, r13d
        sub     eax, edx
        sub     r9d, r10d
        mov     DWORD PTR [r12+r8*2], eax
        mov     WORD PTR [rcx+r8], r9w
        add     r8, 2
        cmp     r8, 131072
        jne     .L2
        pop     rbx
        pop     rbp
        pop     r12
        pop     r13
        ret

你可以看到大部分的GP寄存器都用上了(我没数过),包括< code>rbp、< code>rsi和< code>rdi。< br >寄存器的使用都不局限于它们的规范形式。

注意:在此示例中,rsirdi 用于加载和读取(两者都针对每个寄存器)数组,这是巧合。
这些寄存器用于传递前两个整数/指针参数。

int sum(int a, int b, int c, int d)
{
    return a+b+c+d;
}

sum(int, int, int, int):
        lea     eax, [rdi+rsi]
        add     eax, edx
        add     eax, ecx
        ret
 类似资料:
  • 在x86-64中,如果某些通用寄存器比其他寄存器更受欢迎,某些指令会执行得更快吗? 例如,会比执行得更快吗?我可以想象后者需要一个REX前缀,这会使指令获取速度变慢? 使用代替怎么样?或呢?其他操作?更小的寄存器,如vs?vs? AMD vs Intel?更新的处理器?较旧的处理器?指令的组合? 澄清:某些通用登记册是否应该优先于其他登记册,它们是哪些?

  • 2.1 通用 CPU 寄存器 CPU 的寄存器能够对少量的数据进行快速的存取访问。在 x86 指令集里,一个 CPU 有 八个通用寄存器:EAX, EDX, ECX, ESI, EDI, EBP, ESP 和 EBX。还有很多别的寄存器,遇 到的时候具体讲解。这八个通用寄存器各有不同的用途,了解它们的作用对于我们设计调试 器是至关重要的。让我们先简略的看一看每个寄存器和功能。最后我们将通过一个简单

  • 问题内容: 普通CPU(例如Android设备)是基于寄存器的计算机。Java虚拟机是基于堆栈的计算机。但是基于堆栈的计算机是否依赖于基于寄存器的计算机工作?由于基于堆栈的计算机不是OS,因此不能单独运行吗?除了JVM,是否有任何基于堆栈的计算机示例?有人说1个操作数,2个操作数;你为什么需要这个? 问题答案: JVM在任何地方都没有提到寄存器的存在。从它的角度来看,内存仅存在于少数几个地方,例如

  • 在Google的Python类中 Python是一种动态的解释(字节码编译)语言 我知道什么是解释器,也知道什么是字节码,但两者加在一起似乎不合适。在阅读了一些之后,我变得更清楚了,基本上Python源代码在被解释之前是自动编译的;但是出现了一些新的问题。 使用Python解释器时,不会发生编译吗?如果有,什么时候?例如,如果您只是在命令行中键入代码,并且每次按enter键时它都会运行,那么编译器

  • 考虑以下x86程序集: 序列结束时,rax的值与输入时的值相同,但从CPU的角度来看,其值取决于从内存加载到rcx的值。特别是,在该加载和两个异或指令完成之前,不会开始后续使用rax。 有什么方法可以比两个异或序列更有效地实现这种效果,例如,使用单个单uop单周期延迟指令?如果某个常量值需要在序列之前设置一次(例如,有一个零寄存器),则可以。

  • 我想你们大多数人都知道在Java语言中是一个保留的关键字,但实际上并没有被使用。你们可能也知道是一个Java虚拟机(JVM)操作码。我认为Java、Scala和静态编程语言的所有复杂的控制流结构都是在JVM级别上使用和、、等的某种组合来实现的。 查看JVM规范https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.