如果使用 memcpy(,,3)
来复制三个字节,优化后的编译代码应该是怎样的?需要注意的是,你需要给出汇编指令,让代码更精简高效。
下面是一个示例程序:
#include <string.h>
int main() {
int* p = (int*) 0x10;
int x = 0;
memcpy(&x, p, 4);
x = x * (x > 1 ? 2 : 3);
memcpy(p, &x, 4);
return 0;
}
这段代码有点牵强,会导致分段错误,但我需要这些指令,以便在使用-O3
编译时不会全部消失。当我编译此代码(GodBolt,GCC 6.3 -O3)时,会出现以下错误:
main:
mov edx, DWORD PTR ds:16
xor eax, eax
cmp edx, 1
setle al
add eax, 2
imul eax, edx
mov DWORD PTR ds:16, eax
xor eax, eax
ret
很好 - 从内存到寄存器的单个DWORD(= 4字节)mov
。非常优化。现在让我们将memcpy(&x, p1, 4)
更改为memcpy(&x, p1, 3)
?编译结果如下:
main:
mov DWORD PTR [rsp-4], 0
movzx eax, WORD PTR ds:16
mov WORD PTR [rsp-4], ax
movzx eax, BYTE PTR ds:18
mov BYTE PTR [rsp-2], al
mov edx, DWORD PTR [rsp-4]
xor eax, eax
cmp edx, 1
setle al
add eax, 2
imul eax, edx
mov DWORD PTR ds:16, eax
xor eax, eax
ret
我不是Intel X86_64汇编的专家(也就是说,当它很复杂时,我甚至无法正确阅读它),因此我不太理解这个问题。我的意思是,我知道前6条指令中发生了什么以及为什么需要这么多指令。为什么两个mov不足够呢?一个
mov WORD PTR
int al
和一个mov BYTE PTR
进入ah
不行吗?......所以,我来这里问。当我写问题时,我注意到GodBolt也有clang选项。好吧,clang(3.9.0 -O3)会做到这一点:
main: # @main
movzx eax, byte ptr [18]
shl eax, 16
movzx ecx, word ptr [16]
or ecx, eax
cmp ecx, 2
sbb eax, eax
and eax, 1
or eax, 2
imul eax, ecx
mov dword ptr [16], eax
xor eax, eax
ret
看起来更像我预期的样子。是什么原因导致了这种差异?
注:
- It's the same behavior essentially if I don't initialize
x = 0
. - Other GCC versions do about the same thing as GCC 6.3, but GCC 7 is down to 5 instead of 6
mov
's. - Other versions of clang (starting from 3.4) do about the same thing.
The behavior is similar if we forego memcpy'ing for the following:
#include <string.h> typedef struct { unsigned char data[3]; } uint24_t; int main() { uint24_t* p = (uint24_t*) 0x30; int x = 0; *((uint24_t*) &x) = *p; x = x * (x > 1 ? 2 : 3); *p = *((uint24_t*) &x); return 0; }
If you want to contrast with what happens when the relevant code is in a function, have a look at this or the uint24_t struct version (GodBolt). Then have a look at what happens for 4-byte values.
int*
参数并在它们之间执行 3 字节的 memcpy。void foo(int *p, int *q) { memcpy(p, q, 3); }
。https://godbolt.org/g/NR4Lcc。或者在 3 字节的 memcpy 之前添加*p = 0
,以获得像您所看到的代码。或者可能让它返回*p
,这样它需要内存中和寄存器中的结果。 - Peter Cordes*p=0
然后再return *p
。 - Peter Cordesx
。除此之外,没有什么新的可看的。只是一些将3个字节复制为2 + 1个字节的操作。 - Peter Cordes