32 位二进制补码表示法 https://en.wikipedia.org/wiki/Two%27s_complement of -1
is 0xFFFFFFFF
(全一)。1 OR x
总是1
,所以这无条件地设置ecx
至-1。这个技巧仅适用于 -1,因为 OR 只能设置位,而不能将它们清除为零。
您引用的解决方案中关于解释的部分“ecx
作为有符号整数 -1",仅在以下 gdb 命令的上下文中才有意义:(gdb) p/d $ecx
->$7 = -1
.
rep
前缀将 ecx 视为无符号计数器。将 ecx 设置为 -1 / UINT_MAX 意味着repne scasb http://www.felixcloutier.com/x86/REP:REPE:REPZ:REPNE:REPNZ.html仅当它在内存中找到零时才会停止,而不是因为ecx
一路倒数。 (理论上,如果没有零,它会倒计时并以这种方式结束,但实际上它会首先出现段错误。-1
不是一个特例rep
).
Why use or
: 代码大小
将寄存器设置为的“正常”方法除零以外的任何值 https://stackoverflow.com/questions/33666617/which-is-best-way-to-set-a-register-to-zero-in-x86-assembly-xor-mov-or-and/33668295#33668295是一个5字节的mov r32, imm32 insn http://www.felixcloutier.com/x86/MOV.html, 例如B9 FF FF FF FF mov ecx,-1
.
如果您更关心代码大小而不是速度,或者您知道对ecx
这里不是问题,您可以使用符号扩展的 8 位立即数来保存两个字节:or r/m32, imm8 http://www.felixcloutier.com/x86/OR.html.
83 C9 FF or ecx, 0FFFFFFFFh
结果中的任何位实际上都不依赖于 ecx 的旧值,因为。然而,真正的 CPU 不会对此进行特殊处理,因此乱序执行直到ecx
准备好了。这是一个对 ecx 旧值的错误依赖. mov
打破了对前一个值的依赖。 (有关此内容的更多信息,请参阅x86 /questions/tagged/x86标签维基,特别是阿格纳·福格的指南 http://www.agner.org/optimize/).
or ecx, imm8
需要一个 ModRM 字节将目标编码为 ecx,这与那种形式不同mov
其中每个目标寄存器都有一个单独的操作码。不幸的是没有操作码mov r/m32, imm8
,这会在许多指令中节省 2 个字节的代码。
如果英特尔愿意放弃向后兼容未记录的指令 http://www.agner.org/optimize/blog/read.php?i=25,他们本来可以添加它。 (8086 没有它,因为在将立即数移动到内存时它只会帮助 16 位代码。他们已经专用了 8 个操作码mov r16, imm16
,在 16 位模式下为 3 个字节,不需要操作数大小前缀,就像不存在的一样mov r/m16, imm8
将会。)
So 在优化代码大小时,这是一个有用的习惯用法,例如对于引导加载程序,或机器代码答案https://codegolf.stackexchange.com/ https://codegolf.stackexchange.com/。 (是的,这是一件事。)
另一个相关的技巧是使用 3 字节lea
如果另一个寄存器中已经有另一个常量,则创建一个常量。例如为了x86-64 Adler32,我需要两个归零寄存器和一个1 https://codegolf.stackexchange.com/questions/78896/compute-the-adler-32-checksum/78972#78972,所以我用了
401120: 31 c0 xor eax,eax
401122: 99 cdq # zero rdx by sign-extending eax (0) into edx
401123: 8d 7a 01 lea edi,[rdx+0x1] # edi=0+1, using a reg + disp8 addressing mode