是的,通常你应该总是使用mov ecx, 9
出于性能原因。它的运行效率比push
/pop
,作为可以在任何端口上运行的单微操作指令。 (Agner Fog 测试过的所有现有 CPU 都是如此:https://agner.org/optimize/ https://agner.org/optimize/)
正常的原因是push imm8
/ pop r32
是机器代码没有零字节。这对于外壳代码必须通过以下方式溢出缓冲区strcpy
或任何其他将其视为隐式长度 C 字符串的一部分的方法,该字符串以 a 结尾0
byte.
mov ecx, immediate
仅适用于 32 位立即数,因此机器代码如下所示B9 09 00 00 00
. vs. 6a 09
按9;59
流行 ecx.
(ECX是寄存器号1
,这就是哪里B9
and 59
来自:指令的低3位=001
)
另一个用例纯粹是代码大小: mov r32, imm32
是 5 个字节(使用 no ModRM 编码,将寄存器编号放在操作码的低 3 位中),因为不幸的是 x86 缺少符号扩展的 imm8 操作码mov
(没有mov r/m32, imm8
)。几乎所有可追溯到 8086 的 ALU 指令都存在这种情况。
在 16 位 8086 中,该编码不会节省任何空间:3 字节短格式mov r16, imm16
和假设一样好mov r/m16, imm8
对于几乎所有事情,除了将立即转移到记忆中mov r/m16, imm16
需要表单(带有 ModRM 字节)。
由于 386 的 32 位模式没有添加特定于该模式的新操作码,只是更改了默认操作数大小和立即数宽度,因此 32 位模式下 ISA 中的这种“错过的优化”从 386 开始。长 2 个字节,add r32,imm32
现在比add r/m32, imm8
. See x86 汇编 16 位与 8 位立即操作数编码 https://stackoverflow.com/questions/56524046/x86-assembly-16-bit-vs-8-bit-immediate-operand-encoding。但我们没有这个选项mov
因为没有 MOV 操作码对其立即数进行符号扩展(或零扩展)。
有趣的事实:clang -Oz
(即使以牺牲速度为代价来优化大小)将编译 https://godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(fontScale:1.2899450879999999,j:1,lang:c%2B%2B,source:%27int+foo()%7Breturn+9%3B%7D%27),l:%275%27,n:%270%27,o:%27C%2B%2B+source+%231%27,t:%270%27)),k:46.68013929135108,l:%274%27,n:%270%27,o:%27%27,s:0,t:%270%27),(g:!((g:!((h:compiler,i:(compiler:clang800,filters:(b:%270%27,binary:%271%27,commentOnly:%270%27,demangle:%270%27,directives:%270%27,execute:%271%27,intel:%270%27,libraryCode:%271%27,trim:%271%27),lang:c%2B%2B,libs:!(),options:%27-Oz%27,source:1),l:%275%27,n:%270%27,o:%27x86-64+clang+8.0.0+(Editor+%231,+Compiler+%231)+C%2B%2B%27,t:%270%27)),header:(),k:42.71263230348839,l:%274%27,m:50,n:%270%27,o:%27%27,s:0,t:%270%27),(g:!((h:compiler,i:(compiler:g91,filters:(b:%270%27,binary:%271%27,commentOnly:%270%27,demangle:%270%27,directives:%270%27,execute:%271%27,intel:%270%27,libraryCode:%271%27,trim:%271%27),lang:c%2B%2B,libs:!(),options:%27-Os%27,source:1),l:%275%27,n:%270%27,o:%27x86-64+gcc+9.1+(Editor+%231,+Compiler+%232)+C%2B%2B%27,t:%270%27)),header:(),l:%274%27,m:50,n:%270%27,o:%27%27,s:0,t:%270%27)),k:53.31986070864893,l:%273%27,n:%270%27,o:%27%27,t:%270%27)),l:%272%27,n:%270%27,o:%27%27,t:%270%27)),version:4 int foo(){return 9;}
to push 9
; pop rax
。 GCC12也支持类似的-Oz
.
也可以看看使用 x86/x64 机器代码打高尔夫球的技巧 https://codegolf.stackexchange.com/questions/132981/tips-for-golfing-in-x86-x64-machine-code/132985#132985Codegolf.SE(一个关于大小优化的网站,通常是为了好玩,而不是为了将代码放入小的 ROM 或引导扇区中。但是对于机器代码,大小优化有时确实有实际应用,即使是以牺牲性能为代价。)
如果您已经有另一个包含已知内容的寄存器,则可以使用 3 字节在另一个寄存器中创建 9lea ecx, [eax-0 + 9]
(如果 EAX 保持0
)。只需 Opcode + ModRM + disp8。因此,如果您已经要对任何其他寄存器进行异或清零,则可以避免推送/弹出黑客攻击。lea
效率几乎不低于mov
,并且在优化速度时您可以考虑它,因为较小的代码大小在大规模中具有较小的速度优势:L1i 缓存命中,有时在 uop 缓存尚未热时进行解码。