代码之家  ›  专栏  ›  技术社区  ›  No Name QA

为什么C++使用32位登记器来存储8位值

  •  1
  • No Name QA  · 技术社区  · 6 年前

    我已经尝试了下面的C++代码:

    void foo( ) {
        char c = 'a';
        c = c + 1;
    }
    

    x86-64 gcc 10.1 default flags :

        mov     BYTE PTR [rbp-1], 97
        movzx   eax, BYTE PTR [rbp-1]  ; EAX here
        add     eax, 1
        mov     BYTE PTR [rbp-1], al
    

    但是得到以下结果 x86-64 djgpp 7.2.0 default flags :

        mov     BYTE PTR [ebp-1], 97
        mov     al, BYTE PTR [ebp-1] ; AL here
        inc     eax
        mov     BYTE PTR [ebp-1], al
    

    EAX 而不是 AL ?

    艾尔 只有

    如果是这样的话,使用32位寄存器作为8位值会导致什么样的性能问题?

    0 回复  |  直到 6 年前
        1
  •  2
  •   Timothy Baldwin    6 年前

    在AMD和最近的Intel处理器上,加载部分寄存器需要整个寄存器的先前值,以便将其与加载的值结合起来生成新的寄存器值。

    如果写入完整寄存器,则不需要旧值,因此,通过寄存器重命名,可以在上一次写入寄存器之前完成。

        2
  •  0
  •   halfer Jatin Pandey    6 年前
    unsigned char fun ( unsigned char a, unsigned char b )
    {
        return(a+b);
    }
    
    Disassembly of section .text:
    
    0000000000000000 <fun>:
       0:   8d 04 3e                lea    (%rsi,%rdi,1),%eax
       3:   c3                      retq  
    
    Disassembly of section .text:
    
    00000000 <fun>:
       0:   e0800001    add r0, r0, r1
       4:   e20000ff    and r0, r0, #255    ; 0xff
       8:   e12fff1e    bx  lr
    
    
    Disassembly of section .text:
    
    00000000 <fun>:
       0:   1840        adds    r0, r0, r1
       2:   b2c0        uxtb    r0, r0
       4:   4770        bx  lr
    
    Disassembly of section .text:
    
    00000000 <fun>:
       0:   952e                    add x10,x10,x11
       2:   0ff57513            andi    x10,x10,255
       6:   8082                    ret
    

    这是一个编译器选择,因此您需要与编译器作者讨论它,而不是堆栈溢出。编译器需要在功能上实现高级语言,因此,在这些情况下,所有这些都有32位GPRs,选择是屏蔽每个操作还是至少在值留待以后使用之前屏蔽,还是假设寄存器脏了,需要在使用之前屏蔽它,或者您是否有架构功能,如eax可以在较小的部分ax、al中访问,并围绕此进行设计?只要它在功能上起作用,任何解决方案都是完美的。

    一个编译器可能会选择将al用于8位操作,另一个编译器可能会选择eax(从性能角度来看,eax可能更有效,您可以阅读关于该主题的内容),在这两种情况下,您都必须为rax/eax/ax寄存器中的剩余位进行设计,而不是稍后再对其进行oops并使用较大的寄存器。

    但要签字:

    signed char fun ( signed char a, signed char b )
    {
        return(a+b);
    }
    
    Disassembly of section .text:
    
    0000000000000000 <fun>:
       0:   8d 04 3e                lea    (%rsi,%rdi,1),%eax
       3:   c3                      retq  
    
    Disassembly of section .text:
    
    00000000 <fun>:
       0:   e0800001    add r0, r0, r1
       4:   e1a00c00    lsl r0, r0, #24
       8:   e1a00c40    asr r0, r0, #24
       c:   e12fff1e    bx  lr
    

    强制它处理此函数中的大小

    signed char fun ( signed char a, signed char b )
    {
        if((a+b)>200) return(1);
        return(0);
    }
    
    Disassembly of section .text:
    
    0000000000000000 <fun>:
       0:   40 0f be f6             movsbl %sil,%esi
       4:   40 0f be ff             movsbl %dil,%edi
       8:   01 f7                   add    %esi,%edi
       a:   81 ff c8 00 00 00       cmp    $0xc8,%edi
      10:   0f 9f c0                setg   %al
      13:   c3                      retq 
    
    Disassembly of section .text:
    
    00000000 <fun>:
       0:   e0800001    add r0, r0, r1
       4:   e35000c8    cmp r0, #200    ; 0xc8
       8:   d3a00000    movle   r0, #0
       c:   c3a00001    movgt   r0, #1
      10:   e12fff1e    bx  lr
    

    因为arm设计知道传入的值已被剪裁,这比他们选择不剪裁的值大,可能是因为我将其保留为已签名。但在x86的情况下,因为它们没有在退出时进行剪辑,所以在进入操作时进行了剪辑。

    unsigned char fun ( unsigned char a, unsigned char b )
    {
        if((a+b)>200) return(1);
        return(0);
    }
    
    Disassembly of section .text:
    
    00000000 <fun>:
       0:   e0800001    add r0, r0, r1
       4:   e35000c8    cmp r0, #200    ; 0xc8
       8:   d3a00000    movle   r0, #0
       c:   c3a00001    movgt   r0, #1
      10:   e12fff1e    bx  lr
    

    现在我不同意,因为例如0xFF+0x01=0x00,它不大于200,但这段代码将以大于200的形式传递它。他们还在无符号比较中使用有符号小于和大于。

    unsigned char fun ( unsigned char a, unsigned char b )
    {
        if(((unsigned char)(a+b))>200) return(1);
        return(0);
    }
    00000000 <fun>:
       0:   e0800001    add r0, r0, r1
       4:   e20000ff    and r0, r0, #255    ; 0xff
       8:   e35000c8    cmp r0, #200    ; 0xc8
       c:   93a00000    movls   r0, #0
      10:   83a00001    movhi   r0, #1
      14:   e12fff1e    bx  lr
    

    啊,这是一些C语言推广活动。(就像浮动f;f=f+1.0;vs f=f+1.0F;)

    Disassembly of section .text:
    
    0000000000000000 <fun>:
       0:   01 fe                   add    %edi,%esi
       2:   40 80 fe c8             cmp    $0xc8,%sil
       6:   0f 97 c0                seta   %al
       9:   c3                      retq 
    

    为什么GCC使用EAX而不是AL?

    为什么djgpp只使用AL?

    是性能问题吗?

    这些是编译器设计的选择,不是问题,不一定是性能问题,而是关于如何使用目标指令集实现高级语言的总体编译器设计。每个编译器都可以自由地按照自己的意愿进行操作,没有理由期望gcc、clang、djgpp和其他编译器具有相同的设计选择,也没有理由期望gcc版本x.x.x和y.y.y具有相同的设计选择,因此,如果您回溯得足够远,那么可能是不同的,也许不是(如果他们有,那么提交可能会解释“为什么”的问题,或者从那时起开发团队的电子邮件会涵盖它)。

    推荐文章