【发布时间】:2016-04-22 16:05:59
【问题描述】:
我想编写一个简单的代码(或算法)来设置/清除溢出标志。对于设置 OF,我知道我可以使用有符号值。但是我该如何清除呢?
【问题讨论】:
-
执行一个永远不会产生溢出的操作?例如
mov ax, 1; inc ax
我想编写一个简单的代码(或算法)来设置/清除溢出标志。对于设置 OF,我知道我可以使用有符号值。但是我该如何清除呢?
【问题讨论】:
mov ax, 1; inc ax
有很多可能的解决方案。
例如,test al, al 将清除OF 标志而不影响寄存器内容。
或者,如果您不想影响其他标志,您可以直接修改*FLAGS 寄存器。例如,在 32 位中,这看起来像:
pushfd ; Push EFLAGS onto the stack
and dword [esp], ~0x800 ; Clear bit 11 (OF)
popfd ; Pop the modified result back into EFLAGS
编辑:根据 Peter Cordes' 的建议将 or al, al 更改为 test al, al。 (效果是一样的,但出于性能原因,后者更好)
【讨论】:
test al,al 是根据寄存器内容设置标志的更好习惯用法。它只写入标志,因此不会增加涉及al 的依赖链的长度。不过,对于 pushf/popf 方法 +1。 (因为sahf/lahf 不包括OF)。
or和test的好点;我相应地更新了我的答案。谢谢!
提供:
CF-Flag说寄存器是al。 (setc 仅适用于字节寄存器 r/8)
; clear OF-Flag, preserve CF
setc al
sar al, 1
注意:这很好,因为它没有部分标志更新,这可能会导致停顿。 (sar xx, 1 写入所有标志,不保留任何未修改的标志,与 inc/dec 不同)c.f. Intel Optimization Guide, 3.5.2.6: Partial Flag Register Stalls,但请注意,现代英特尔 CPU 根本没有部分标志停止或标志合并:读取标志 just read either or both of CF or the SPAZO group as 2 separate inputs 的指令。 (这就是为什么cmovbe 在 Broadwell 和更高版本上仍然是 2 微指令:它需要 CF 和 ZF。https://uops.info/)
来源:Intel Documentation SAR p.1234
说寄存器是al。 (适用于 r/8、r/16、r/32、r/64)
; set OF-Flag, preserve CF
mov al, 0x7F
inc al
; clear OF-Flag, preserve CF
mov al, 0x0
inc al
来源:Intel Documentation INC p.551
不同的方法,如果你可以假设:
adx 的处理器(您可以使用grep adx /proc/cpuinfo 检查cpu 标志)说寄存器是eax。 (需要 r64/r32)
; clear OF-Flag, preserve CF
mov eax, 0x0
adox eax, eax
; set OF-Flag, preserve CF
mov eax, 0xFFFFFFFF
adox eax, eax
注意:不要尝试用xor(或类似的)替换mov,因为这会清除CF
【讨论】:
-1。 0xFFFF... + 1 换行,产生进位(ADOX 将输出为 OF=1)。但是,是的,如果你事先设置了一个不同的值,这是一种有趣的方式来设置/清除 OF 而无需触及其他标志。
adox 也无法处理立即值。我编辑了代码。
popf is quite slow (like one per 20 cycles on Skylake);如果您需要清除或设置 OF 则理想情况下将其作为 ALU 指令的副作用,尤其是您将无论如何都将用于您知道不会或将溢出的有用计算的指令。 (会溢出的通常更难找到,与 CF 不同,您总是可以只使用 sub 而不是 add 使用一个常数,该常数几乎涵盖所有输入,除了非常小的范围)。
如果您出于某种原因需要设置/清除只是 OF而不影响其他条件代码,那么是的,pushf/popf 是要走的路。 lahf / sahf 没有得到 OF,因为 OF 是 EFLAGS 中的第 11 位,在低位 8 之外。
test al,al(或任何相同、相同的寄存器)清除 OF 和 CF,就像 comparing / subtracting zero。根据值设置其他标志是有用的。
xor eax,eax 清除 EAX,清除 OF/SF/CF,设置 ZF/PF。无论如何,您通常都需要一个归零的寄存器,因此如果您需要 OF 清除(例如,对于 adox extended-precision chain 的开头),然后用一块石头杀死 2 只鸟并安排您的代码,以便最后一个标志设置指令是异或归零。
在 x86-64 中,您还可以相信在指针 + 长度上使用 add 不会越过无符号虚拟地址空间的中间,因此会清除 OF。但是这种假设可能会在未来具有完全 64 位虚拟地址的 CPU 上被打破,因为那样就没有hole in virtual address space around the signed-wraparound boundary,因此单个连续数组可以跨越它。这已经可以在 32 位代码中发生,在 64 位内核或不使用 2G:2G 内核的 32 位内核下运行:虚拟地址空间的用户分割。
xor eax, eax/cmp al, -128设置OF,只占用4个字节的代码。这可能是最便宜的方式,并且与sub 或其他方式不同,它不写入任何部分寄存器(或任何完整寄存器)。它仍然使 EAX 归零。
0 - -128 wraps to -128, i.e. signed OF。 8 位 2 的补码整数只能表示来自 -128..+127 的值。最负数是一种特殊情况,没有适当的逆数。它是它自己的绝对值/负值,或者更准确地说是那些函数溢出。 (或者您可以将绝对值运算视为有符号输入和无符号输出,因此结果为 +128,即 0x80。x86 没有整数 abs 指令(准备 -x,然后是 test/cmov),但是对于 SSSE3,它确实有向量整数 pabsb)
对于 AL 中除 -1 之外的任何已知值,都有一个 cmp al, imm8 将设置 OF。对于从 0..127 开始的任何值,cmp al, -128 会换行。对于 -2..-128 中的任何值,cmp al, +127 进行换行并因此设置 OF。对于-1,减去 127 只会得到 -128。减去 -128 会得到 +127。不幸的是,我认为在寄存器中没有已知值的情况下,没有一种单指令方式来设置 OF。
al 没有,但有一个 2 字节的特殊编码 cmp al,imm8。其他 8 位或 32 位寄存器可以使用正常的 3 字节编码。
没有破坏任何寄存器,也没有已知的常量,这是 6 个字节:
push rax
xor eax,eax
cmp al, -128
pop rax
这确实破坏了其他条件代码,但它比pushf/popf 快。不过,通常你可以破坏一些东西,否则你不能破坏堆栈。
setno al # OF=0 -> AL=1 OF=1 -> AL=0
cmp al, -127 # 1 - -127 = 128 = -128 0 - -127 = +127
【讨论】:
fstsw [bp-2]/pop ax/lahf。或286 and laterfstsw ax/lahf
fstsw ax/SAHF(将AH存储到FLAGS)/ja或jb或jp。 (LAHF/SAHF 助记符对我来说似乎倒退了;按此顺序编写,我的大脑认为将 AH 加载到 FLAGS,而不是从 FLAGS)。无论如何,这就是为什么最近的 fucomi 和 SSE ucomiss / ucomisd 指令设置标志,如无符号比较,PF 设置为无序 (NaN);为了兼容the legacy method lined up the C3/C2/C0 bits in the x87 status word with bits in FLAGS。