为什么GCC将字复制到返回寄存器中,而不是字节?

为什么GCC将字复制到返回寄存器中,而不是字节?,c,gcc,optimization,x86-64,C,Gcc,Optimization,X86 64,GCC(4.4.7)没有直接将字节从结构移动到%eax,这是有逻辑原因的,还是仅仅是优化疏忽 考虑以下计划: struct foo { unsigned char x; }; struct bar { unsigned int x; }; int foo (const struct foo *x, int y) { return x->x * y; } int bar (const struct bar *x, int y) { return x->x * y; } 使用GCC编

GCC(4.4.7)没有直接将字节从结构移动到
%eax
,这是有逻辑原因的,还是仅仅是优化疏忽

考虑以下计划:

struct foo { unsigned char x; };
struct bar { unsigned int x; };

int foo (const struct foo *x, int y) { return x->x * y; }
int bar (const struct bar *x, int y) { return x->x * y; }
使用GCC编译时,
foo()
bar()
的差异比我预期的要大:

foo:
.LFB0:
        .cfi_startproc
        movzbl  (%rdi), %edx
        movl    %esi, %eax
        imull   %edx, %eax
        ret
        .cfi_endproc

bar:
.LFB1:
        .cfi_startproc
        movl    (%rdi), %eax
        imull   %esi, %eax
        ret
        .cfi_endproc
我期望
foo()
bar()
一样,只是使用了不同的移动指令


我要注意的是,在
clang-500.2.79
下,编译器生成我所期望的
foo()
的代码,并且
foo()
bar()
具有相同数量的指令(正如我所期望的GCC一样,但是是错误的)。

因为在函数foo中乘以uchar x和uint y,编译器必须首先将uchar x升级为int,指令movzbl就是这样做的

之后,我使用gcc 4.6.1和-O3选项重新编译了您的代码,得到了如下汇编:

foo:
.LFB34:
    .cfi_startproc
    movzbl  (%rdi), %eax
    imull   %esi, %eax
    ret 
    .cfi_endproc

bar:
.LFB35:
    .cfi_startproc
    movl    (%rdi), %eax
    imull   %esi, %eax
    ret 
    .cfi_endproc
它不再使用%edx了。


简短的回答

为什么GCC将字复制到返回寄存器中,而不是字节

因为你要求它返回一个单词而不是一个字节。编译器根据您的代码执行了要求的操作。在一种情况下,您要求提升尺码,但在两种情况下,您都要求取消签名以进行签名。实现这一点的方法不止一种,而clang/llvm和gcc恰好有所不同

GCC(4.4.7)没有将字节从结构直接移动到%eax中是否有逻辑原因,或者这只是一个优化疏忽

我认为,根据我们在当前编译器中看到的情况,这是一个疏忽。请参阅下面生成的代码。(-每种情况下使用的氧气)


与这个问题相关的有趣实验

struct foo { unsigned char x; };
struct bar { unsigned int x; };

char foo (const struct foo *x, char y) { return x->x * y; }
char bar (const struct bar *x, char y) { return x->x * y; }
叮当声

叮当声

gcc和clangforx86使用非指定字符生成与上面相同的代码,但是

手臂


最后一个指令集对读者来说是一个练习,在反汇编中有一点线索…

该指令与
条中的
movl
相匹配,尽管-这不是差异中有趣的部分…您是说进入
%edx
的基本原理是整数提升,但是进入
%eax
也会进行升级,并允许删除一条指令。之后我尝试使用gcc 4.6.3和-O3选项编译这些代码,我发现从foo生成的汇编就像bar一样,没有使用%edx。@dwelch:谢谢你的建议。在使用小型内存系统时,需要将内容存储为字节(甚至位)。我原以为在使用这些字节进行算术运算时,使用这些字节不会对CPU周期数造成重大影响,但指令大小增加33%并非无关紧要,因此问题就来了。@nicky_zs:谢谢,这意味着
4.4
优化得不够好。你能用
4.6
作为你答案的一部分发布你的发现吗?注意:我希望
foo()
做一个
.x
int
(标准促销),然后
int
*
int
(确实如此),而我希望
bar()
执行
y
无符号
,然后执行
无符号
*
无符号
。(穆尔对伊穆尔)。Hmmm@chux:我认为这是有符号整数溢出未定义的情况之一,因此编译器可以选择这样做。问题是编译器并不完美,即使是好的编译器也不会总是选择你本该选择的、没有指出的指令,但根据我研究编译器输出的经验,像gcc或clang这样的编译器有时会更好,有时会更差。有时选择非常好的解决方案有时会被绊倒非常糟糕。通常,如果你选择利用或浪费时间取决于你对它的感觉,当然是在C中,您可以操纵编译器来执行某些操作。在这种情况下,大小在一种情况下跳跃,然后是无符号和有符号的,这要求编译器进行这些转换,并为编译器明智地或不添加更多指令打开了大门。如果要隔离一个目标,可以进行大量调优,但也可以对任何目标进行常规调优(不要在代码中进行太多转换)。
0000000000000000 <foo>:
   0:   0f b6 07                movzbl (%rdi),%eax
   3:   0f af c6                imul   %esi,%eax
   6:   c3                      retq   

0000000000000010 <bar>:
  10:   8b 07                   mov    (%rdi),%eax
  12:   0f af c6                imul   %esi,%eax
  15:   c3                      retq   
00000000 <foo>:
   0:   90820000    lbu v0,0(a0)
   4:   00000000    nop
   8:   00450018    mult    v0,a1
   c:   00001012    mflo    v0
  10:   03e00008    jr  ra
  14:   00000000    nop

00000018 <bar>:
  18:   8c820000    lw  v0,0(a0)
  1c:   00000000    nop
  20:   00a20018    mult    a1,v0
  24:   00001012    mflo    v0
  28:   03e00008    jr  ra
  2c:   00000000    nop
00000000 <foo>:
   0:   e5d00000    ldrb    r0, [r0]
   4:   e0000091    mul r0, r1, r0
   8:   e12fff1e    bx  lr

0000000c <bar>:
   c:   e5900000    ldr r0, [r0]
  10:   e0000091    mul r0, r1, r0
  14:   e12fff1e    bx  lr
struct foo { unsigned char x; };
struct bar { unsigned int x; };

char foo (const struct foo *x, char y) { return x->x * y; }
char bar (const struct bar *x, char y) { return x->x * y; }
0000000000000000 <foo>:
   0:   8a 07                   mov    (%rdi),%al
   2:   40 f6 e6                mul    %sil
   5:   0f be c0                movsbl %al,%eax
   8:   c3                      retq   

0000000000000010 <bar>:
  10:   0f af 37                imul   (%rdi),%esi
  13:   40 0f be c6             movsbl %sil,%eax
  17:   c3                      retq   
0000000000000000 <foo>:
   0:   89 f0                   mov    %esi,%eax
   2:   f6 27                   mulb   (%rdi)
   4:   c3                      retq   

0000000000000010 <bar>:
  10:   89 f0                   mov    %esi,%eax
  12:   f6 27                   mulb   (%rdi)
  14:   c3                      retq   
00000000 <foo>:
   0:   e5d00000    ldrb    r0, [r0]
   4:   e0010190    mul r1, r0, r1
   8:   e20100ff    and r0, r1, #255    ; 0xff
   c:   e12fff1e    bx  lr

00000010 <bar>:
  10:   e5900000    ldr r0, [r0]
  14:   e0010190    mul r1, r0, r1
  18:   e20100ff    and r0, r1, #255    ; 0xff
  1c:   e12fff1e    bx  lr
00000000 <foo>:
   0:   90820000    lbu v0,0(a0)
   4:   00052e00    sll a1,a1,0x18
   8:   00052e03    sra a1,a1,0x18
   c:   00a20018    mult    a1,v0
  10:   00001012    mflo    v0
  14:   00021600    sll v0,v0,0x18
  18:   03e00008    jr  ra
  1c:   00021603    sra v0,v0,0x18

00000020 <bar>:
  20:   8c820000    lw  v0,0(a0)
  24:   00052e00    sll a1,a1,0x18
  28:   00052e03    sra a1,a1,0x18
  2c:   00a20018    mult    a1,v0
  30:   00001012    mflo    v0
  34:   00021600    sll v0,v0,0x18
  38:   03e00008    jr  ra
  3c:   00021603    sra v0,v0,0x18
struct foo { unsigned char x; };
struct bar { unsigned int x; };

unsigned char foo (const struct foo *x, unsigned char y) { return x->x * y; }
unsigned char bar (const struct bar *x, unsigned char y) { return x->x * y; }
00000000 <foo>:
   0:   e5d00000    ldrb    r0, [r0]
   4:   e0010190    mul r1, r0, r1
   8:   e20100ff    and r0, r1, #255    ; 0xff
   c:   e12fff1e    bx  lr

00000010 <bar>:
  10:   e5900000    ldr r0, [r0]
  14:   e0010190    mul r1, r0, r1
  18:   e20100ff    and r0, r1, #255    ; 0xff
  1c:   e12fff1e    bx  lr
00000000 <foo>:
   0:   90820000    lbu v0,0(a0)
   4:   30a500ff    andi    a1,a1,0xff
   8:   00a20018    mult    a1,v0
   c:   00001012    mflo    v0
  10:   03e00008    jr  ra
  14:   304200ff    andi    v0,v0,0xff

00000018 <bar>:
  18:   8c820000    lw  v0,0(a0)
  1c:   30a500ff    andi    a1,a1,0xff
  20:   00a20018    mult    a1,v0
  24:   00001012    mflo    v0
  28:   03e00008    jr  ra
  2c:   304200ff    andi    v0,v0,0xff
00000000 <_foo>:
   0:   1166            mov r5, -(sp)
   2:   1185            mov sp, r5
   4:   9f40 0004       movb    *4(r5), r0
   8:   45c0 ff00       bic $-400, r0
   c:   1001            mov r0, r1
   e:   7075 0006       mul 6(r5), r1
  12:   1040            mov r1, r0
  14:   1585            mov (sp)+, r5
  16:   0087            rts pc

00000018 <_bar>:
  18:   1166            mov r5, -(sp)
  1a:   1185            mov sp, r5
  1c:   1d41 0006       mov 6(r5), r1
  20:   707d 0004       mul *4(r5), r1
  24:   1040            mov r1, r0
  26:   1585            mov (sp)+, r5
  28:   0087            rts pc
gcc --version
gcc (Ubuntu/Linaro 4.8.1-10ubuntu9) 4.8.1
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

clang --version
clang version 3.4 (branches/release_34 201060)
Target: x86_64-unknown-linux-gnu
Thread model: posix

arm-none-eabi-gcc --version
arm-none-eabi-gcc (GCC) 4.8.2
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

mips-elf-gcc --version
mips-elf-gcc (GCC) 4.8.2
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.