6

我想编写一个简单的代码(或算法)来设置/清除溢出标志。对于设置 OF,我知道我可以使用有符号值。但我怎样才能清除呢?

4

3 回答 3

3

有许多可能的解决方案。

例如,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

编辑:更改or al, altest al, al根据Peter Cordes的建议。(效果相同,但出于性能原因,后者更好)

于 2016-04-22T17:05:05.070 回答
3

假如:

  • 你有一个你不关心其内容的寄存器,
  • 你必须保留CF-Flag

清除 OF (sar) 的最佳解决方案:

说寄存器是al。(setc仅适用于字节寄存器 r/8)

; clear OF-Flag, preserve CF
setc al
sar al, 1

注意:这很好,因为它没有部分标志更新,这可能会导致停顿。(sar xx, 1写入所有标志,不保留任何未修改的标志,与inc/不同dec)参见英特尔优化指南,3.5.2.6:部分标志寄存器停顿,但请注意现代英特尔 CPU 根本没有部分标志停顿或标志合并:指令读取 FLAGS只需将 CF 或 SPAZO 组中的一个或两个读取为 2 个单独的输入。(这就是为什么cmovbe在 Broadwell 和更高版本上仍然是 2 uops:它需要 CF 和 ZF。https: //uops.info/

来源:英特尔文档 SAR p.1234

一般解决方案(inc/dec):

说寄存器是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

资料来源:英特尔文档公司 p.551

或者(阿多克斯):

不同的方法,如果你可以假设:

  • 一个adx启用的处理器(你用 来检查 CPU 标志grep adx /proc/cpuinfo

说寄存器是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 

注意:不要尝试替换movxor(或类似的),因为这会清除CF

来源:英特尔文档 ADOX p.150

于 2020-07-17T02:27:59.003 回答
2

popf非常慢(就像 Skylake 上每 20 个周期一个);如果您需要清除或设置 OF 则理想情况下将其作为 ALU 指令的副作用,尤其是您将无论如何都将用于您知道不会或将溢出的有用计算的指令。(会溢出的通常更难找到,这与 CF 不同,在 CF 中你总是可以用一个常数来sub代替add几乎所有输入的几乎所有输入,除了一个非常小的范围)。

如果您出于某种原因需要设置/清除OF 而不会影响其他条件代码,那么是的, pushf/popf是要走的路。 lahf/sahf没有得到 OF,因为 OF​​ 是 EFLAGS 中的第 11 位,在低 8 之外。


test al,al(或任何相同,相同的寄存器)清除 OF 和 CF,就像比较/减去零一样。其他标志根据值有用地设置。

xor eax,eax清除 EAX,清除 OF/SF/CF,设置 ZF/PF。无论如何,您通常都需要一个归零的寄存器,因此如果您需要 OF 清除(例如,用于adox扩展精度链的开始),然后用一块石头杀死 2 只鸟并安排您的代码,以便最后一个标志设置指令是异或归零.

在 x86-64 中,您还可以相信 using addon a pointer + length 不会跨越无符号虚拟地址空间的中间,因此会清除OF. 但是这种假设可能会在未来具有完全 64 位虚拟地址的 CPU 上被打破,因为这样就不会在有符号环绕边界周围的虚拟地址空间中出现漏洞,因此单个连续数组可以跨越它。这已经可以在 32 位代码中发生,在 64 位内核或不使用 2G:2G 内核的 32 位内核下运行:虚拟地址空间的用户分割。


xor eax, eax/cmp al, -128集 OF,并且只需要 4 个字节的代码。这可能是最便宜的方式,并且不同的sub是,它不写入任何部分寄存器(或任何完整寄存器)。它仍然使 EAX 归零。

0 - -128换行至-128,即签署 OF。8 位 2 的补码整数只能表示-128..+127. 最负数是一种特殊情况,没有适当的逆数。它是它自己的绝对值/负值,或者更准确地说是那些函数溢出。(或者您可以将绝对值运算视为有符号输入和无符号输出,因此结果为 +128,即 0x80。x86 没有整数 abs 指令(准备 a -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
于 2018-03-27T03:35:29.663 回答