我已经尝试了以下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为什么GCC不使用AL而使用EAX
为什么djgpp只使用AL?
是性能问题吗?
如果是这样的话,使用32位寄存器作为8位值背后的性能问题是什么?
发布于 2020-07-10 02:13:41
在AMD和最新的Intel处理器上,加载部分寄存器需要整个寄存器的先前值,以便将其与加载的值相结合,以生成新的寄存器值。
如果写入全部寄存器,则不需要旧值,因此,通过寄存器重命名,可以在寄存器的前一次写入之前完成。
发布于 2020-07-09 19:26:58
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不同的目标都来自于gcc。
这是一个编译器选择,所以您需要与编译器作者讨论它,而不是Stack Overflow。编译器需要在功能上实现高级语言,所以在所有这些都有32位GPRs的情况下,选择是屏蔽每个操作,或者至少在值留下来供以后使用之前,还是假设寄存器是脏的,在使用它之前需要屏蔽它,或者你是否有像eax这样的架构功能,可以在较小的部分ax,al中访问,并围绕它进行设计?只要它在功能上工作,任何解决方案都是完全没有问题的。
一个编译器可能选择使用al进行8位操作,另一个编译器可能选择eax (从性能角度来看,这可能更有效,在这一主题上有一些东西可以阅读),在这两种情况下,您都必须为rax/eax/ax寄存器中的剩余位进行设计,并且以后不会对其进行oops,而是使用更大的寄存器。
如果你没有这个部分寄存器访问的选项,你很需要在功能上实现代码,而最简单的方法是做掩码的事情。这将与本例中的C代码相匹配,有人可能会认为x86代码有错误,因为它使用了eax,但没有裁剪,所以不会返回无符号字符。
不过,请签名:
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语言推广的事了。(就像float f;f=f+1.0;vs f=f+1.0F;)
这也改变了x86的结果
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有相同的设计选择,所以如果你追溯到足够远的地方,也许它是不同的,也可能不是(如果他们有,那么也许提交解释了“为什么”的问题,那时的开发小组电子邮件将会涵盖它)。
https://stackoverflow.com/questions/62808748
复制相似问题