【问题标题】:How are booleans "notted" in assembly?布尔值如何在汇编中“注释”?
【发布时间】:2012-12-12 16:24:52
【问题描述】:

我知道在类 C 语言中,我可以不使用 not 运算符执行布尔运算:(C)

int myBool = TRUE;
myBool = !myBool;

但我的问题是:这是如何在幕后实现的?我的猜测是使用跳转,但如果过度使用这些可能效率低下:(Intel x86 语法)

; assume eax holds boolean
  test eax, eax
  jnz short boolTrue
  inc eax ; eax was 0, now 1
  jmp short after
boolTrue: ; eax non-zero
  xor eax, eax ; eax now 0
after:

如图所示,它需要 5 条指令,其中至少有一个跳转和一个逐位and (test)。必须有一种更简单的方法来做到这一点,因为我已经看到出于某种奇怪的原因执行“双重非”(if (!!fileHandle)) 的代码库。

所以(如上所述):编译器如何在 x86 上执行布尔 !s?

【问题讨论】:

  • 通常你甚至不会“拥有”一个布尔值,它会成为标志的一部分。

标签: assembly compilation x86 boolean-logic


【解决方案1】:

您可以使用不需要分支的SETZ(设置(如果)零(设置标志))来做到这一点。

【讨论】:

  • 例如:_Bool myBool = TRUE; myBool = !myBool; printf("%d", myBool); 可能会被编译为 mov eax /*henceforth myBool*/, 1; test eax, eax; setnz eax; push str_format; push eax; call _printf;,如果您的编译器真的天真。
  • 我不认为这是正确的。我不得不使用setz。逻辑如下:test eax, eax; setnz eax 表示,如果eax 不为零(也称为 1),则将 eax 设置为 1。如果 eax 为零,则将 eax 设置为零。这听起来就像一个身份函数是eax 是零或一。所以,我很困惑这是怎么回事。
  • @EnricoBorba - 哈..我认为你是对的。现在我有一些棘手的问题要问过去的我。谢谢!
【解决方案2】:

有可能使用进位和无分支序列:

sub %eax, 1        ;; this produces carry only when %eax EQ zero
sbb %eax, %eax     ;; 0 when not carry, -1 when carry
and %eax, 1        ;; just get the least significant bit (also neg %eax will do)

对于eax=!eax

sub %eax, 1        ;; 
sbb %eax, %eax     
add %eax, 1        ;; [-1, 0] + 1 == [0, 1]

eax = !!eax

我不知道是否有 3 个指令(或更少)序列使用不会浪费原始变量(或寄存器 eax)的常用算术运算。

【讨论】:

    【解决方案3】:

    在一个好的编译器中,实现诸如 NOT 之类的运算符的方法通常不会仅限于一种代码生成模式。编译器将考虑周围上下文的许多因素,例如编译时优化、源代码和/或紧接在 NOT 运算符之前的生成代码、NOT 运算符是否用于条件上下文(例如,在 if 语句中使用)或值上下文(用于赋值)。

    当在 if 语句中使用时,NOT 运算符更有可能使用分支来实现,因为 if 语句很可能无论如何都需要分支。在值上下文中使用时,NOT 运算符更有可能通过比较和捕获结果来实现(没有分支)。

    当在较大的表达式中使用 NOT 运算符时,通常可以反转嵌套运算符,因此以一种方式看待它,NOT 运算符根本不会生成任何代码;这可能发生在像a = ! (b > 0)if ( ! ( a && b ) ) 这样的表达式中。即使使用if ( !a ),您也可能会发现a 实际上并没有被NOT'ed,而只是像if ( a ) 一样被测试,尽管分支条件颠倒了。

    在您给出的具体示例中,我希望编译器尽最大努力(为生产而不是为调试生成代码),它将在编译时确定常量 0 而不是 myBool传递给printf

    简而言之,一个体面的编译器有许多可用的技术和许多标准来确定使用哪个。

    【讨论】:

      【解决方案4】:

      如果您有正确的布尔值,您可以使用XOR 在两种表示之间切换。请注意,C 没有布尔值,它只是将零解释为假,其他任何东西都解释为真。 C++C# 都有正确的布尔值,因此它们不需要弄乱非零检查,实际上至少 g++ 确实使用了 XOR 方法。

      【讨论】:

      • 但是这个呢:xor rax, 1?将有 8 个 字节 仅用于 1,所以这将是一个 10(?) 字节指令
      • 但我知道这将用于在标志枚举中翻转一点。
      • @ColeJohnson 幸运的是,xor eax, 1 只有 3 个字节 (83 F0 01),可以在 01 之间切换。顺便说一句,唯一需要 64 位立即数的指令是 mov r64, imm64
      • 嗯。猜猜我从事 x86-64 程序集的时间不够长,无法了解很多。
      • 哦,是的,有一个 reg32/mem32,imm8 过载。叹息。
      猜你喜欢
      • 1970-01-01
      • 2021-03-08
      • 1970-01-01
      • 2019-09-03
      • 2018-05-06
      • 1970-01-01
      • 1970-01-01
      • 2020-04-11
      • 2011-02-05
      相关资源
      最近更新 更多