您有一个不错的选择:
需要在函数进入/退出时清除 DF(私有辅助函数除外,它可以使用自定义调用约定)。想要使用的功能可以在任何或他们之前std
简单地使用。cld
call
ret
使用其他标志作为返回值的一部分是微不足道的:cld
并且std
不影响它们。
在极少数情况下,您需要在按降序遍历的循环内进行函数调用,可能根本不使用lods
或其他字符串指令。它们不是魔术,偶尔dec si
/mov al, [si]
或其他任何事情都不是代码大小的灾难。或者它意味着一个cld
andstd
在循环内,每个只有 1 个字节。
大多数情况下,您希望清除 DF 以便向上循环,在这种情况下,您可以在循环内进行函数调用而不会出现任何问题。(并非所有时候,但这种设计最适合常见情况,处理不常见情况也不会太痛苦)。
一个相当不错的选择:
- 将所有标志(包括 DF)调用破坏(如果需要,可以用作返回值的一部分)。每个字符串 op 都需要一个
cld
orstd
在同一个函数中。所以这真的不是一个很好的选择。
还有一个平庸的选择:
- 您当前的调用约定,其中 DF 保留调用并且在 function entry 具有未知值。每个使用字符串指令的函数都需要一个
cld
orstd
因为它不能假设任何东西,以及一个 FLAGS 的保存/恢复。(除非您根据已知的调用者和他们将 DF 放入的状态进行优化)。
唯一具有优势的情况是在包含也需要设置 DF 的函数调用的循环中。
当您想在 FLAGS 的条件代码中返回状态以及保存/恢复 DF 时,只需将在 FLAGS 中设置条件代码的指令放在恢复调用者的 DF之后popf
。
在条件代码中没有有趣状态的函数中,只需使用popf
来恢复所有调用者 FLAGS。在不返回 FLAGS 中任何有趣内容的函数中,您不需要将调用者的 DF 合并到当前函数的 FLAGS 中。
在极少数情况下,您无法轻松地将最后一个字符串指令移动到最后一个标志设置指令之前,模拟字符串指令可能会更小。而不是 inc 或 dec,用 . 保持标志不变lea si, [si +- 1]
。(SI 和 DI 在 16 位寻址模式下均有效,因此lea
可用于它们。)
lods
//没有一个是神奇的stos
,movs
甚至它们的rep
版本也没有。如果您不关心性能(仅代码大小),您甚至可以rep movs
在不接触标志的情况下进行仿真,使用慢速loop
指令和备用寄存器(如果需要,使用push
/保存/恢复一个)pop
0B 9C pushfw
0C 8066FFFB and byte [bp-1], 0FBH ; Strip DF from MSB of EFLAGS
10 9D popfw
您的代码示例不会恢复调用者的 DF,它会无条件地清除它。它相当于cld
.
要恢复调用者的 DF,您需要从 first 中提取 DF 位pushf
,将其合并到pushf
函数末尾的 FLAGS 值中,然后 popf
. 这显然是可能的,但比您显示的效率低得多(并且代码大小更大)。
另请注意,这popf
很慢:在 Haswell 上它是 9 微指令,并且每 18 个周期有一个吞吐量。如果您只关心代码大小而不是性能,那么需要pushf
/popf
的设计不一定是坏的,但在我看来,在进入/退出时要求 DF 清除将在大多数情况下赢得代码大小以及性能。
这是 32 位和 64 位调用约定为处理 DF 所选择的,我不明白为什么它在 16 位代码中也不能很好地工作。