【问题标题】:Boolean multiplication in c++?c++中的布尔乘法?
【发布时间】:2012-12-11 15:37:26
【问题描述】:

考虑以下几点:

inline unsigned int f1(const unsigned int i, const bool b) {return b ? i : 0;}
inline unsigned int f2(const unsigned int i, const bool b) {return b*i;}

f2 的语法更紧凑,但标准是否保证f1f2 严格等价?

此外,如果我希望编译器在编译时知道bi 的情况下优化这个表达式,我应该更喜欢哪个版本?

【问题讨论】:

  • 如果不指定哪个编译器,您将无法知道“编译器”将如何处理表达式(在编译时已知参数的情况下),但它很可能会优化两者。一种判断方法是,使用编译时已知的参数编译每个参数并检查输出。
  • 查看生成的汇编代码
  • FWIW,MSVC 为第一种情况生成一个掩码并添加,为第二种情况生成一个乘法,在这种情况下我阻止它通过布尔值本身的评估来玩诡计(如果可以,它将更改产生布尔值的测试以进一步优化)。本质上,MSVC 在任何一种情况下都是无分支的,并且在幼稚的情况下似乎更优化。
  • 您可以将intbool 相乘。在较高的警告级别,您有时会收到性能警告。通常您将bool 转换为int!!return (!!b)*i;。这将使编译器在抱怨时感到高兴。

标签: c++ boolean ternary-operator standards-compliance


【解决方案1】:

嗯,是的,两者是等价的。 bool 是一个整数类型,true 保证在整数上下文中转换为1,而false 保证转换为0

(反之亦然,即非零整数值保证在布尔上下文中转换为true,而零整数值保证在布尔上下文中转换为false。)

由于您使用的是无符号类型,因此您可以轻松地想出其他可能基于 bit-hack 但完全可移植的同一事物的实现,例如

i & -(unsigned) b

虽然体面的编译器应该能够为您的任何版本自行选择最佳实现。

附:尽管令我大吃一惊的是,GCC 4.1.2 几乎从字面上编译了所有三个变体,即它在基于乘法的变体中使用了机器乘法指令。在?: 变体上使用cmovne 指令使其无分支,这很可能使其成为最有效的实现。

【讨论】:

  • 您的“bit-hack”究竟是如何成为“完美可移植的实现”的?该标准不强制要求有符号整数的二进制补码表示(至少目前还没有),因此如果移植到不使用它的系统,它将以奇怪的方式分崩离析。此外,正如您所展示的,一个好的编译器会在优化时执行此类特定于平台的 hack,而不是要求用户使用此类低级细节和假设来搞乱他们的代码。
  • @underscore_d:你在说什么?您在上面的代码中在哪里看到“有符号整数”,您在哪里看到对二进制补码表示的依赖?你在说一元-吗?一元 - 在无符号上下文中的行为不依赖于有符号表示。
  • 你是对的;我一定是半睡半醒......我刚刚在另一个答案上评论了类似的事情,确实bool转换为签名的int,我没有注意到你在做什么它不同而正确。
【解决方案2】:

是的。可以安全地假设 true1false0 在表达式中使用并保证:

C++11,整体促销,4.5

bool 类型的右值可以转换为 int 类型的右值,使用 假变零,真变一。

【讨论】:

    【解决方案3】:

    编译器将使用隐式转换从b 生成unsigned int,所以,是的,这应该可以工作。您通过简单的乘法跳过条件检查。哪个更有效/更快?不知道。一个好的编译器很可能会优化我假设的两个版本。

    【讨论】:

      【解决方案4】:

      FWIW,如下代码

      inline unsigned int f1(const unsigned int i, const bool b) {return b ? i : 0;}
      inline unsigned int f2(const unsigned int i, const bool b) {return b*i;}
      
      int main()
      {
          volatile unsigned int i = f1(42, true);
          volatile unsigned int j = f2(42, true);
      }
      

      用 gcc -O2 编译产生这个程序集:

          .file   "test.cpp"
          .def    ___main;    .scl    2;  .type   32; .endef
          .section    .text.startup,"x"
          .p2align 2,,3
          .globl  _main
          .def    _main;  .scl    2;  .type   32; .endef
      _main:
      LFB2:
          .cfi_startproc
          pushl   %ebp
          .cfi_def_cfa_offset 8
          .cfi_offset 5, -8
          movl    %esp, %ebp
          .cfi_def_cfa_register 5
          andl    $-16, %esp
          subl    $16, %esp
          call    ___main
          movl    $42, 8(%esp)   // i
          movl    $42, 12(%esp)  // j
          xorl    %eax, %eax
          leave
          .cfi_restore 5
          .cfi_def_cfa 4, 4
          ret
          .cfi_endproc
      LFE2:
      

      如您所见,f1f2 所剩无几。

      就 C++ 标准而言,允许编译器在优化方面做任何事情,只要它不改变可观察的行为(as if 规则)。

      【讨论】:

      • 为了产生一个有代表性的结果,你可能应该对函数 f1(rand(), rand()) 使用不可预测的(由编译器)参数会是一个更好的例子。
      猜你喜欢
      • 1970-01-01
      • 2013-11-29
      • 1970-01-01
      • 2015-06-17
      • 2018-07-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-13
      相关资源
      最近更新 更多