如何将寄存器清零?有几种方法?

41

我很好奇在x86汇编中有多少种方法可以使用一个指令将寄存器设置为零。有人告诉我他至少找到了10种方法。

我能想到的方法有:

xor ax,ax
mov ax, 0
and ax, 0

3
我很想知道为什么有些人投票关闭这个问题。谢谢。 - user173973
3
"sub ax, ax :)" 的意思是将寄存器ax中的值减去它自身,即将ax清零。接着的 "shr ax, 16; mul ax,0" 指令将ax向右移动16位(相当于将ax除以65536),然后将ax乘以0,最终结果仍为0。 - bestsss
1
毫无疑问,有许多方法可以做到这一点,但不幸的是,我必须投票关闭此问题,因为这样的问题的实用性似乎过于狭窄:这个问题只与一个小地理区域、一个特定的时间点或一个非常狭窄的情况相关,这些情况并不适用于互联网的全球受众。 - paxdiablo
2
@paxdiablo 在常见问题解答中找不到类似的内容。在这种情况下,每个人都应该成为Python/Java程序员。 - user173973
1
@paxdiablo 最快的是众所周知的“异或”,但背后有趣的事情是:为什么其他操作实际上更慢(或影响标志位),对我来说这是相当实用的,因为它反映了CPU设计并提供了其他情况的见解。 - bestsss
显示剩余11条评论
10个回答

19

有很多种可能的方法可以在IA32下将0移动到ax中...

    lea eax, [0]
    mov eax, 0FFFF0000h         //All constants form 0..0FFFFh << 16
    shr  ax, 16                 //All constants form 16..31
    shl eax, 16                 //All constants form 16..31

也许这是最奇怪的... :)

@movzx:
    movzx eax, byte ptr[@movzx + 6]   //Because the last byte of this instruction is 0

同时在32位模式下(指令更长,最终(最高有效)地址字节后移)也是如此...

  @movzx:
    movzx ax, byte ptr[@movzx + 7]

编辑:

对于16位x86 CPU模式,未经测试...:

    lea  ax, [0]

并且...

  @movzx:
    movzx ax, byte ptr cs:[@movzx + 7]   //Check if 7 is right offset

cs:前缀在ds段寄存器不等于cs段寄存器的情况下是可选的。


@GJ。shr eax, 16指令不能保证清除AX寄存器!使用shr ax, 16即可。您的movzx ax, byte ptr cs:[@movzx + 7]指令用于16位实地址模式,使用了正确的偏移量7,但我认为您应该明确说明需要一个地址大小前缀(67h)才能使32位位移的高字为零。 - Sep Roland
当原始高位非零时,eax >>= 16 会使低16位(AX)非零。它会使EAX的16位为0,但那不是AX。 - Peter Cordes
1
你将movzx的编辑改为了movzx eax,而不是ax,使用movzx eax,byte ptr[@movzx + 7]导致它出现错误(除非你指的是在16位模式下使用67h前缀,但这并没有暗示,并且你在注释中也没有提到)。该指令只有7个字节长,这就是为什么前一个代码块中相同的指令使用$ + 6而不是$ + 7来访问disp32的最后一个字节。 - Peter Cordes
应注意movzx是386+指令,因此在所有x86-16机器上均无法使用。 - ecm

14

请参考这个回答了解寄存器清零的最佳方法:xor eax,eax(具有性能优势和更小的编码)。


我将只考虑一条指令可以清零一个寄存器的方式。如果允许从内存中加载零值,那么方法就太多了,因此我们将大多数排除掉从内存中加载的指令。
我发现有10种不同的单个指令可以清零32位寄存器(因此在长模式下也会清零完整的64位寄存器),没有预先条件或从任何其他内存中加载。这不包括相同insn的不同编码,或mov的不同形式。如果计算从已知保存零值的内存或从段寄存器等加载,则有大量的方法。还有无数种方法可以清零向量寄存器。
对于其中大多数,eax和rax版本是相同功能的不同编码,两者都将全64位寄存器清零,隐式地清零上半部分或使用REX.W前缀显式写入完整寄存器。
整数寄存器(NASM语法):
# Works on any reg unless noted, usually of any size.  eax/ax/al as placeholders
and    eax, 0         ; three encodings: imm8, imm32, and eax-only imm32
andn   eax, eax,eax   ; BMI1 instruction set: dest = ~s1 & s2
imul   eax, any,0     ; eax = something * 0.  two encodings: imm8, imm32
lea    eax, [0]       ; absolute encoding (disp32 with no base or index).  Use [abs 0] in NASM if you used DEFAULT REL
lea    eax, [rel 0]   ; YASM supports this, but NASM doesn't: use a RIP-relative encoding to address a specific absolute address, making position-dependent code

mov    eax, 0         ; 5 bytes to encode (B8 imm32)
mov    rax, strict dword 0   ; 7 bytes: REX mov r/m64, sign-extended-imm32.    NASM optimizes mov rax,0 to the 5B version, but dword or strict dword stops it for some reason
mov    rax, strict qword 0   ; 10 bytes to encode (REX B8 imm64).  movabs mnemonic for AT&T.  normally assemblers choose smaller encodings if the operand fits, but strict qword forces the imm64.

sub    eax, eax       ; recognized as a zeroing idiom on some but maybe not all CPUs
xor    eax, eax       ; Preferred idiom: recognized on all CPUs
                      ; 2 same-size encodings each: r/m, r  vs.  r, r/m

@movzx:
  movzx eax, byte ptr[@movzx + 6]   //Assuming the high byte of the absolute address is 0.  Not position-independent, and x86-64 RIP+rel32 would load 0xFF

.l: loop .l             ; clears e/rcx... eventually.  from I. J. Kennedy's answer.  To operate on only ECX, use an address-size prefix.
; rep lodsb             ; not counted because it's not safe (potential segfaults), but also zeros ecx

指令xor reg,reg可以有两种不同的编码方式(可参考此链接)。在GAS AT&T语法中,我们可以请求汇编器选择哪个操作码。这仅适用于允许两种形式的reg,reg整数指令,即追溯到8086年的指令。因此不适用于SSE / AVX。

  {load}  xor %eax, %eax           # 31 c0
  {store} xor %eax, %eax           # 33 c0

"Shift all the bits out one end" 对于常规大小的 GP 寄存器不可能实现,只能对部分寄存器进行操作。在 286 及以后的处理器上,shlshr 的移位计数会被掩码 (详见链接): count & 31; 即取模 32。
(立即数移位是从 186 开始引入的,之前只有 CL 和隐含的 1,因此存在一些无掩码立即移位的 CPU(包括 NEC V30)。另外,286 及更早版本的处理器仅支持 16 位,因此 ax 是一个“全”寄存器。还有一些 CPU 可以通过移位将整个整数寄存器清零。)
另外请注意,矢量的移位计数会饱和而不是环绕。
# Zeroing methods that only work on 16bit or 8bit regs:
shl    ax, 16           ; shift count is still masked to 0x1F for any operand size less than 64b.  i.e. count %= 32
shr    al, 16           ; so 8b and 16b shifts can zero registers.

# zeroing ah/bh/ch/dh:  Low byte of the reg = whatever garbage was in the high16 reg
movxz  eax, ah          ; From Jerry Coffin's answer

根据其他现有条件(除了在另一个寄存器中为零):

bextr  eax,  any, eax  ; if al >= 32, or ah = 0.  BMI1
BLSR   eax,  src       ; if src only has one set bit
CDQ                    ; edx = sign-extend(eax)
sbb    eax, eax        ; if CF=0.  (Only recognized on AMD CPUs as dependent only on flags (not eax))
setcc  al              ; with a condition that will produce a zero based on known state of flags

PSHUFB   xmm0, all-ones  ; xmm0 bytes are cleared when the mask bytes have their high bit set

向量寄存器

其中一些SSE2整数指令也可以用于MMX寄存器(mm0 - mm7)。我不会单独展示它们。

同样,最好的选择是某种形式的异或运算。可以使用PXOR / VPXORXORPS / VXORPS。有关详细信息,请参见在x86汇编中将寄存器设置为零的最佳方法:xor、mov还是and?

AVX vxorps xmm0,xmm0,xmm0 可以将整个ymm0 / zmm0清零,并且在AMD CPU上比vxorps ymm0,ymm0,ymm0

这些清零指令每个都有三种编码方式:传统SSE、AVX(VEX前缀)和AVX512(EVEX前缀)。不过,SSE版本只会清零底部的128位,而在支持AVX或AVX512的CPU上,这并不是完整的寄存器。无论如何,根据计算方法不同,每个条目可以是三个不同的指令(尽管它们具有相同的操作码,只是前缀不同)。除了vzeroall之外,AVX512没有改变其他指令(也不会清零zmm16-31)。
PXOR       xmm0, xmm0     ;; recommended
XORPS      xmm0, xmm0     ;; or this
XORPD      xmm0, xmm0     ;; longer encoding for zero benefit
PXOR       mm0, mm0     ;; MMX, not show for the rest of the integer insns

ANDNPD    xmm0, xmm0
ANDNPS    xmm0, xmm0
PANDN     xmm0, xmm0     ; dest = ~dest & src

PCMPGTB   xmm0, xmm0     ; n > n is always false.
PCMPGTW   xmm0, xmm0     ; similarly, pcmpeqd is a good way to do _mm_set1_epi32(-1)
PCMPGTD   xmm0, xmm0
PCMPGTQ   xmm0, xmm0     ; SSE4.2, and slower than byte/word/dword

PSADBW    xmm0, xmm0     ; sum of absolute differences
MPSADBW   xmm0, xmm0, 0  ; SSE4.1.  sum of absolute differences, register against itself with no offset.  (imm8=0: same as PSADBW)

  ; shift-counts saturate and zero the reg, unlike for GP-register shifts
PSLLDQ    xmm0, 16       ;  left-shift the bytes in xmm0
PSRLDQ    xmm0, 16       ; right-shift the bytes in xmm0
PSLLW     xmm0, 16       ; left-shift the bits in each word
PSLLD     xmm0, 32       ;           double-word
PSLLQ     xmm0, 64       ;             quad-word
PSRLW/PSRLD/PSRLQ  ; same but right shift

PSUBB/W/D/Q   xmm0, xmm0     ; subtract packed elements, byte/word/dword/qword
PSUBSB/W   xmm0, xmm0     ; sub with signed saturation
PSUBUSB/W  xmm0, xmm0     ; sub with unsigned saturation

;; SSE4.1
INSERTPS   xmm0, xmm1, 0x0F   ; imm[3:0] = zmask = all elements zeroed.
DPPS       xmm0, xmm1, 0x00   ; imm[7:4] => inputs = treat as zero -> no FP exceptions.  imm[3:0] => outputs = 0 as well, for good measure
DPPD       xmm0, xmm1, 0x00   ; inputs = all zeroed -> no FP exceptions.  outputs = 0

VZEROALL                      ; AVX1  x/y/zmm0..15 not zmm16..31
VPERM2I/F128  ymm0, ymm1, ymm2, 0x88   ; imm[3] and [7] zero that output lane

# Can raise an exception on SNaN, so only usable if you know exceptions are masked
CMPLTPD    xmm0, xmm0         # exception on QNaN or SNaN, or denormal
VCMPLT_OQPD xmm0, xmm0,xmm0   # exception only on SNaN or denormal
CMPLT_OQPS ditto

VCMPFALSE_OQPD xmm0, xmm0, xmm0   # This is really just another imm8 predicate value for the same VCMPPD xmm,xmm,xmm, imm8 instruction.  Same exception behaviour as LT_OQ.

SUBPS xmm0, xmm0等类似指令无法正常工作,因为NaN-NaN = NaN,而不是零。

此外,FP指令可能会在NaN参数上引发异常,因此即使是CMPPS/PD,只有当您知道异常被屏蔽,并且您不关心在MXCSR中可能设置的异常位时,才是安全的。即使是AVX版本,由于其扩展的谓词选择,也会在SNaN上引发#IA。 “静默”谓词仅对QNaN抑制#IA。 CMPPS/PD还可能引发Denormal异常。(AVX512 EVEX编码可以抑制FP异常,对于512位向量,同时覆盖舍入模式)

(请参见CMPPD指令集参考条目中的表格,或更好的是在英特尔原始PDF中查看,因为HTML提取会破坏该表格。)

AVX1/2 和 AVX512 EVEX 版本中的 PXOR 操作:这些操作都会将 ZMM 目标寄存器的所有位清零。PXOR 有两个 EVEX 版本:VPXORD 或 VPXORQ,允许使用 dword 或 qword 元素进行掩码操作。(XORPS/PD 已经在助记符中区分了元素大小,所以 AVX512 没有改变这一点。在传统的 SSE 编码中,对于所有 CPU,XORPD 都是一个毫无意义的代码浪费(较大的操作码),与 XORPS 相比。)
VPXOR      xmm15, xmm0, xmm0      ; AVX1 VEX
VPXOR      ymm15, ymm0, ymm0      ; AVX2 VEX, less efficient on some CPUs
VPXORD     xmm31, xmm0, xmm0      ; AVX512VL EVEX
VPXORD     ymm31, ymm0, ymm0      ; AVX512VL EVEX 256-bit
VPXORD     zmm31, zmm0, zmm0      ; AVX512F EVEX 512-bit

VPXORQ     xmm31, xmm0, xmm0      ; AVX512VL EVEX
VPXORQ     ymm31, ymm0, ymm0      ; AVX512VL EVEX 256-bit
VPXORQ     zmm31, zmm0, zmm0      ; AVX512F EVEX 512-bit

不同的向量宽度在Intel's PXOR manual entry中列出了单独的条目。
您可以使用零屏蔽(但不能使用合并屏蔽)与任何掩码寄存器,无论您从屏蔽还是从向量指令的正常输出中获取零值都无关紧要。但这不是一个不同的指令。例如:VPXORD xmm16{k1}{z}, xmm0, xmm0 AVX512:
这里可能有几个选项,但我现在不太好奇,不想浏览指令集列表查找所有选项。

有一个有趣的值得一提,即:VPTERNLOGD/Q可以使用imm8 = 0xFF将寄存器设置为全1, 但是在当前实现中对旧值存在虚假依赖。由于比较指令都会比较成掩码,根据我的测试,在Skylake-AVX512上,VPTERNLOGD似乎是将向量设置为全1的最佳方法,尽管它不会特殊处理imm8 = 0xFF的情况以避免虚假依赖

VPTERNLOGD zmm0, zmm0,zmm0, 0     ; inputs can be any registers you like.

掩码寄存器(k0..k7)清零:掩码指令和向量比较入掩码。
kxorB/W/D/Q     k0, k0, k0     ; narrow versions zero extend to max_kl
kshiftlB/W/D/Q  k0, k0, 100    ; kshifts don't mask/wrap the 8-bit count
kshiftrB/W/D/Q  k0, k0, 100
kandnB/W/D/Q    k0, k0, k0     ; x & ~x

; compare into mask
vpcmpB/W/D/Q    k0, x/y/zmm0, x/y/zmm0, 3    ; predicate #3 = always false; other predicates are false on equal as well
vpcmpuB/W/D/Q   k0, x/y/zmm0, x/y/zmm0, 3    ; unsigned version

vptestnmB/W/D/Q k0, x/y/zmm0, x/y/zmm0       ; x & ~x test into mask      

x87浮点数:

只有一种选择(因为如果旧值为无穷大或NaN,则sub函数无法工作)。

FLDZ    ; push +0.0

1
@ecm:谢谢,我的一些旧答案仍然存在错误信息,例如来自NASM附录旧版本的信息。在这里重写了那段话。 - Peter Cordes
我只是在想,但是在某些情况下,将寄存器清零实际上需要解决停机问题吗?假设我们有一个循环,在某个条件下,将“ax”的单个位清零,并循环直到“ax”的16位实际上为“0”。在这种情况下,要知道“ax”是否会取值为“0”意味着知道循环最终是否停止。 - ljleb
@Louis-JacobLebel:我们可以在这里删除我们的评论,但它们并不是关于问题的主题。在我看来,简而言之就是:是的,你所描述的是停机问题的一个子集,并且与此问题或答案无关。 - Peter Cordes
"shr al, 16; 所以8位和16位的移位可以将寄存器清零。你是不是在这一行中想使用8而不是16?" - Alexis Wilke
@AlexisWilke:没有特别的,尽管任何低至8的数字仍会将AL清零。从31到16的任何计数都将清零比32位窄的寄存器。8b是指操作数大小,而不是移位计数。在286和186上,较低的移位计数速度更快(8086不支持立即移位),但如果您关心性能,您应该使用mov al, 0xor ax,ax - Peter Cordes
显示剩余6条评论

4
另外还有两种可能性:
sub ax, ax

movxz, eax, ah

编辑:我应该指出,movzx并没有将eax的所有位都清零,它只是清零了ah(以及作为寄存器本身不可访问的前16位)。

至于速度最快的问题,如果我没记错,subxor是等效的。它们比(大多数)其他指令更快,因为它们很常见,CPU设计者为它们添加了特殊优化。具体来说,对于普通的subxor,结果取决于寄存器中的先前值。CPU会特别识别与自身异或和自身相减,以便知道依赖关系链在那里被打破。之后的任何指令都不会依赖于任何先前的值,因此它可以使用重命名寄存器并行执行先前和后续指令。

特别是在旧处理器上,我们预计'mov reg, 0'会更慢,因为它有额外的16位数据,而早期处理器(尤其是8088)主要受限于从内存加载流的能力。实际上,在8088上,您可以使用任何参考表格相当准确地估计运行时间,只需注意所涉及的字节数即可。这对于dividiv指令确实会失效,但仅限于此。另一方面,我应该保持沉默,因为8088对大多数人来说真的没有什么兴趣(至少已经有十年了)。


2
使用零扩展移动(386+) :) - user173973
我还记得8088,真的很喜欢它,16位-哇(与6502相比)。在使用6502之前,我有一个坏习惯,只使用ah / al等。计算时钟是一次很有趣的冒险。至于sub / xor,两者都应该是3个时钟,但现在我记不起来了。 - bestsss

3

这个帖子很老了,但还有一些其他的例子。其中简单的有:

xor eax,eax

sub eax,eax

and eax,0

lea eax,[0] ; it doesn't look "natural" in the binary

更复杂的组合:

; flip all those 1111... bits to 0000
or  eax,-1  ;  eax = 0FFFFFFFFh
not eax     ; ~eax = 0

; XOR EAX,-1 works the same as NOT EAX instruction in this case, flipping 1 bits to 0
or  eax,-1  ;  eax = 0FFFFFFFFh
xor eax,-1  ; ~eax = 0

; -1 + 1 = 0
or  eax,-1 ;  eax = 0FFFFFFFFh or signed int = -1
inc eax    ;++eax = 0

3

使用LOOP $可以将寄存器CX设置为0。


如果您可以清除其他寄存器,那么请使用 REP CMPSB。 - I. J. Kennedy
3
repe cmpsbrepne cmpsb都可能导致rcx/ecx/cx非零;同样的,scas也是如此。前缀rep可以与lodsmovsstosinsouts一起使用,以将计数器寄存器设置为零。正如您正确提到的那样,这些指令具有超出更改计数器寄存器的效果。正如Peter Cordes所提到的,由于访问内存,rep lods指令可能会出现故障。 - ecm

3

当然,特定情况下还有其他将寄存器设置为0的方法:例如,如果您将eax设置为正整数,则可以使用cdq/cltdedx设置为0(这个技巧在一种著名的24字节shellcode中使用,该shellcode出现在“通过示例学不安全编程”中)。


2
评论中,OP写道移位不能使用立即计数(在80186/80286中引入)。因此,目标x86 CPU必须是8086/8088。(10年前,这个问题肯定更好地标记为[8086],而不是最近(5年?)引入的[x86-16])
8086架构提供了14个基本程序执行寄存器,用于一般系统和应用程序编程。这些寄存器可以分为以下几组:
• 通用寄存器 AX、BX、CX、DX、SI、DI、BP 和 SP。这八个寄存器可用于存储操作数和指针。
• 段寄存器 CS、DS、ES 和 SS。这些寄存器允许寻址超过64KB的内存。
• 标志寄存器 FLAGS。该寄存器报告正在执行的程序的状态,并允许在应用程序级别控制处理器。
• IP 寄存器。该指令指针寄存器包含一个16位指向下一条要执行的指令的指针。

因此,在x86上清除寄存器的答案可以处理上述任何寄存器中的零,当然不能清除FLAGS寄存器,因为其结构定义在第二位始终保持为1。

接下来是单个指令列表,可以在8086上清除寄存器,并且不依赖于任何现有条件。列表按字母顺序排列:

encoding         instruction                register cleared           displacement
--------------   ---------------            -----------------------    ------------
25 00 00         and     ax, 0              AX
83 E0 00         and     ax, 0              AX BX CX DX SI DI BP SP
81 E0 00 00      and     ax, 0              AX BX CX DX SI DI BP SP
E8 -- --         call    0000h              IP                         -($+3)
9A 00 00 xx yy   call    yyxxh:0000h        IP
9A xx yy 00 00   call    0000h:yyxxh        CS
9A 00 00 00 00   call    0000h:0000h  (*)   IP and CS
E9 -- --         jmp     0000h              IP                         -($+3)
EA 00 00 xx yy   jmp     yyxxh:0000h        IP
EA xx yy 00 00   jmp     0000h:yyxxh        CS
EA 00 00 00 00   jmp     0000h:0000h  (*)   IP and CS
8D 06 00 00      lea     ax, [0000h]        AX BX CX DX SI DI BP SP
F3 AC            rep lodsb                  CX
F3 AD            rep lodsw                  CX
E2 FE            loop    $                  CX
B8 00 00         mov     ax, 0              AX BX CX DX SI DI BP SP
C7 C0 00 00      mov     ax, 0              AX BX CX DX SI DI BP SP
F3 A4            rep movsb            (*)   CX
F3 A5            rep movsw            (*)   CX
F3 AA            rep stosb            (*)   CX
F3 AB            rep stosw            (*)   CX
29 C0            sub     ax, ax             AX BX CX DX SI DI BP SP
2B C0            sub     ax, ax             AX BX CX DX SI DI BP SP
31 C0            xor     ax, ax             AX BX CX DX SI DI BP SP
33 C0            xor     ax, ax             AX BX CX DX SI DI BP SP

此列表展示了技术上可能实现的内容,但并不代表您应该使用这些内容。带有(*)标记的指令非常危险或只能谨慎使用。
显而易见的是,要使calljmp正常工作,您需要在目标位置拥有可执行代码。
清除通用寄存器的最佳方法是使用xor reg, reg,如果您不想更改任何标志,则使用mov reg, 0

1
嘿,jmp/call 0000h:0000h 比靠近的 jmp/call 0 到段的开头略微不太可用。你需要在 IVT 通常开始的线性地址 0 处有机器码,但是在代码序列中间跳转到 IP=0 不是你可以做的事情。(除非有一个 ret 在那里从调用返回)。实际上,第一个 IVT 条目是 #DE 除法异常,所以只要你不触发它,你就可以在线性地址 0 处有一个 retf。(如果你不跳回,你会想在用代码覆盖 IVT 的其余部分之前禁用中断。) - Peter Cordes

2

根据DEF CON 25 - XlogicX - Assembly Language is Too High Level所述:

基于0的立即操作数执行AAD指令将始终将AH清零,并保留AL不变。根据Intel's pseudocode,其伪代码如下:
AL ← (oldAL + (oldAH ∗ imm8)) AND FFH;

在汇编源码中:

AAD 0         ; assemblers like NASM accept this

db 0xd5,0x00  ; others many need you to encode it manually

显然(至少在某些CPU上),在bswap eax前面加上一个66操作数大小前缀(即尝试编码bswap ax66 0F C8)会将AX清零。

3
指定其他进制给 AADAAM 指令在某些机器上不被支持,例如 NEC V20/V30。 - ecm

0
如果你在8086上使用8位值,那么最快清除al的方法是使用“mov al, ah”,这需要2个周期。而“xor al, al”和“xor ax, ax”都需要3个周期。当然,你必须确保ah已经为0。

1
记住,code-fetch每个块需要4个周期(8088字节,8086字),因此对于字节寄存器而言,xor-zeroing仍然比填充缓冲区要快,即使在8086上也是如此。mov al, 0只有2个字节的机器代码,但是https://www2.math.uni-wuppertal.de/~fpf/Uebungen/GdR-SS02/opcode_i.html将其列为8088上的4个周期(对于8位或16位的mov reg,imm),尽管该表列出的机器代码大小是错误的,应该是1 + i(1,2)用于非ModRM短编码。无论如何,该表格没有考虑到code-fetch,这是8088的主要瓶颈,也是8086的一部分。 - Peter Cordes
有关8086 / 8088上的性能问题,请参阅提高二进制到格雷码的效率:8086 - Peter Cordes

-2
mov eax,0  
shl eax,32  
shr eax,32  
imul eax,0 
sub eax,eax 
xor eax,eax   
and eax,0  
andn eax,eax,eax 

loop $ ;ecx only  
pause  ;ecx only (pause="rep nop" or better="rep xchg eax,eax")

;twogether:  
push dword 0    
pop eax

or eax,0xFFFFFFFF  
not eax

xor al,al ;("mov al,0","sub al,al",...)  
movzx eax,al
...

1
这不是 rep 的工作方式;对于除“字符串”指令(它不适用于该指令)之外的指令,它将被忽略,或者在像 pausetzcnt 这样的指令中,它实际上是指令码的一部分,而这些指令是针对了解它们的 CPU。此外,x86 使用 & 0x1f(模32)屏蔽标量移位的移位计数,或使用模64屏蔽64位操作数大小。您只能移出16或8位寄存器的所有位。请参阅我在此问题上的答案,该答案发布于此之前2年。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接