【问题标题】:got an unexpected answer from the x?y:z expression从 x?y:z 表达式中得到了意想不到的答案
【发布时间】:2013-05-13 19:25:39
【问题描述】:

这是一个简单的 C++ sn-p:

int x1 = 10, x2 = 20, y1 = 132, y2 = 12, minx, miny, maxx, maxy;
x1 <= x2 ? minx = x1, maxx = x2 : minx = x2, maxx = x1;
y1 <= y2 ? miny = y1, maxy = y2 : miny = y2, maxy = y1;
cout << "minx=" << minx << "\n";
cout << "maxx=" << maxx << "\n";
cout << "miny=" << miny << "\n";
cout <<" maxy=" << maxy << "\n";

我认为结果应该是:

minx=10
maxx=20
miny=12
maxy=132

但实际上结果是:

minx=10
maxx=10
miny=12
maxy=132

谁能解释为什么maxx 不是20?谢谢。

【问题讨论】:

  • 括号解决问题...
  • 又一个理由是“不要试图变得聪明”,而是使用if 来代替条件表达式。无论哪种方式,编译器都会做同样的事情[假设您添加了相关的括号,以便它执行您真正想要的操作]。三元表达式有时可能很有用,但这是一个很好的例子,说明不要使用它们。
  • (无论如何,为什么不呢:maxx = x1 &gt; x2 ? x1 : x2?
  • 为什么不#include&lt;algorithm&gt;maxi = std::max(x1, x2)
  • 当我第一次看到这个问题的标题时,我以为是going to be about PHP

标签: c++ c operator-precedence conditional-operator comma-operator


【解决方案1】:

在 C++11 中,您可以使用 std::tiestd::make_pair 来使这个明显正确的一目了然 (TM)

#include <tuple>
#include <utility>
#include <iostream>

using namespace std;

int main()
{
    int x1 = 10, x2=20, y1=132, y2=12, minx, miny, maxx, maxy;

    tie(minx, maxx) = (x1 <= x2)? make_pair(x1, x2) : make_pair(x2, x1);
    tie(miny, maxy) = (y1 <= y2)? make_pair(y1, y2) : make_pair(y2, y1);

    cout<<"minx="<<minx<<"\n";
    cout<<"maxx="<<maxx<<"\n";
    cout<<"miny="<<miny<<"\n";
    cout<<"maxy="<<maxy<<"\n";
}

在线output

这在语义上等同于发布的所有其他解决方案,并且使用任何合适的优化编译器,都没有任何开销。它在语法上更好,因为它有

  • 最少的代码重复,
  • 4 个赋值给变量都在赋值的左侧,并且
  • 4 个赋值自变量都在右侧。

作为一种可以概括为查找指向序列的最小和最大元素的指针的细微变化,您可以使用 std::minmax_element 以及原始数组具有非成员函数 begin()end() 的事实(均为 C++ 11 个功能)

#include <algorithm>
#include <tuple>
#include <iostream>

using namespace std;

int main()
{
    int x[] = { 10, 20 }, y[] = { 132, 12 }, *minx, *miny, *maxx, *maxy;

    tie(minx, maxx) = minmax_element(begin(x), end(x));
    tie(miny, maxy) = minmax_element(begin(y), end(y));

    cout<<"minx="<<*minx<<"\n";
    cout<<"maxx="<<*maxx<<"\n";
    cout<<"miny="<<*miny<<"\n";
    cout<<"maxy="<<*maxy<<"\n";
}

在线output.

【讨论】:

    【解决方案2】:

    条件运算符的优先级大于逗号运算符,所以

    x1<=x2 ? minx=x1,maxx=x2 : minx=x2,maxx=x1;
    

    用括号括起来

    (x1<=x2 ? minx=x1,maxx=x2 : minx=x2),maxx=x1;
    

    因此,无论条件如何,最后一次赋值都会完成。

    要修复它,您可以

    • 使用括号:

      x1 <= x2 ? (minx = x1, maxx = x2) : (minx = x2, maxx = x1);
      

      (您不需要true 分支中的括号,但 IMO 最好也有它们)。

    • 使用两个条件句:

      minx = x1 <= x2 ? x1 : x2;
      maxx = x1 <= x2 ? x2 : x1;
      
    • 使用if:

      if (x1 <= x2) {
          minx = x1;
          maxx = x2;
      } else {
          minx = x2;
          maxx = x1;
      }
      

    无论是否经过优化编译,if 版本和带括号的带逗号的单个条件都会在 gcc (4.7.2) 和 clang (3.2) 下生成相同的程序集,因此也可以合理地期望其他编译器也会这样做。带有两个条件的版本会产生不同的程序集,但经过优化,这两个编译器也只发出一个 cmp 指令。

    在我看来,if 版本最容易验证其正确性,因此更可取。

    【讨论】:

    • +1 用于测试生成代码的差异并提供可读性参数。
    【解决方案3】:

    虽然其他人已经解释了问题的原因是什么,但我认为“更好”的解决方案应该是使用 if 编写条件:

    int x1 = 10, x2=20, y1=132, y2=12, minx, miny, maxx, maxy;
    if (x1<=x2) 
    { 
       minx=x1;
       maxx=x2;
    }
    else
    {
       minx=x2; 
       maxx=x1;
    }
    if (y1<=y2)
    {
        miny=y1;
        maxy=y2;
    } 
    else 
    {
        miny=y2;
        maxy=y1;
    }
    

    是的,它长了几行,但也更容易阅读和明确发生了什么(如果您需要在调试器中单步执行,您可以轻松查看它的运行方式)。

    任何现代编译器都应该能够将其中任何一个转换为相当有效的条件赋值,可以很好地避免分支(从而避免“错误的分支预测”)。

    我准备了一个小测试,我用

    编译
    g++ -O2 -fno-inline -S -Wall ifs.cpp
    

    这是源代码(我必须将其设为参数以确保编译器不只是直接计算正确的值而只是执行mov $12,%rdx,但实际上做了比较并决定更大):

    void mine(int x1, int x2, int y1, int y2)
    {
        int minx, miny, maxx, maxy;
        if (x1<=x2) 
        { 
        minx=x1;
        maxx=x2;
        }
        else
        {
        minx=x2; 
        maxx=x1;
        }
        if (y1<=y2)
        {
        miny=y1;
        maxy=y2;
        } 
        else 
        {
        miny=y2;
        maxy=y1;
        }
    
        cout<<"minx="<<minx<<"\n";
        cout<<"maxx="<<maxx<<"\n";
        cout<<"miny="<<miny<<"\n";
        cout<<"maxy="<<maxy<<"\n";
    }
    
    void original(int x1, int x2, int y1, int y2)
    {
        int minx, miny, maxx, maxy;
        x1<=x2 ? (minx=x1,maxx=x2) : (minx=x2,maxx=x1);
        y1<=y2 ? (miny=y1,maxy=y2) : (miny=y2,maxy=y1);
        cout<<"minx="<<minx<<"\n";
        cout<<"maxx="<<maxx<<"\n";
        cout<<"miny="<<miny<<"\n";
        cout<<"maxy="<<maxy<<"\n";
    }
    
    void romano(int x1, int x2, int y1, int y2)
    {
        int  minx, miny, maxx, maxy;
    
        minx = ((x1 <= x2) ? x1 : x2);
        maxx = ((x1 <= x2) ? x2 : x1);
        miny = ((y1 <= y2) ? y1 : y2);
        maxy = ((y1 <= y2) ? y2 : y1);
        cout<<"minx="<<minx<<"\n";
        cout<<"maxx="<<maxx<<"\n";
        cout<<"miny="<<miny<<"\n";
        cout<<"maxy="<<maxy<<"\n";
    }
    
    int main()
    {
        int x1=10, x2=20, y1=132, y2=12;
        mine(x1, x2, y1, y2);
        original(x1, x2, y1, y2);
        romano(x1, x2, y1, y2);
        return 0;
    }
    

    生成的代码如下所示:

    _Z4mineiiii:
    .LFB966:
        .cfi_startproc
        movq    %rbx, -32(%rsp)
        movq    %rbp, -24(%rsp)
        movl    %ecx, %ebx
        movq    %r12, -16(%rsp)
        movq    %r13, -8(%rsp)
        movl    %esi, %r12d
        subq    $40, %rsp
        movl    %edi, %r13d
        cmpl    %esi, %edi
        movl    %edx, %ebp
        cmovg   %edi, %r12d
        cmovg   %esi, %r13d
        movl    $_ZSt4cout, %edi
        cmpl    %ecx, %edx
        movl    $.LC0, %esi
        cmovg   %edx, %ebx
        cmovg   %ecx, %ebp
            .... removed actual printout code that is quite long and unwieldy... 
    _Z8originaliiii:
        movq    %rbx, -32(%rsp)
        movq    %rbp, -24(%rsp)
        movl    %ecx, %ebx
        movq    %r12, -16(%rsp)
        movq    %r13, -8(%rsp)
        movl    %esi, %r12d
        subq    $40, %rsp
        movl    %edi, %r13d
        cmpl    %esi, %edi
        movl    %edx, %ebp
        cmovg   %edi, %r12d
        cmovg   %esi, %r13d
    movl    $_ZSt4cout, %edi
    cmpl    %ecx, %edx
    movl    $.LC0, %esi
    cmovg   %edx, %ebx
    cmovg   %ecx, %ebp
            ... print code goes here ... 
    _Z6romanoiiii:
        movq    %rbx, -32(%rsp)
        movq    %rbp, -24(%rsp)
        movl    %edx, %ebx
        movq    %r12, -16(%rsp)
        movq    %r13, -8(%rsp)
        movl    %edi, %r12d
        subq    $40, %rsp
        movl    %esi, %r13d
        cmpl    %esi, %edi
        movl    %ecx, %ebp
        cmovle  %edi, %r13d
        cmovle  %esi, %r12d
    movl    $_ZSt4cout, %edi
    cmpl    %ecx, %edx
    movl    $.LC0, %esi
    cmovle  %edx, %ebp
    cmovle  %ecx, %ebx
            ... printout code here.... 
    

    如您所见,mineoriginal 是相同的,romano 使用稍微不同的寄存器和不同形式的cmov,但除此之外它们在相同数量的指令中执行相同的操作。

    【讨论】:

    • +1。问题中发布的代码正是使许多组织完全禁止三元运算符的代码。三元运算符有它的位置,但贴出的用途不是那个位置。
    • Re 是的,它长了几行 -- 如果你使用 1TBS 就不会那么糟糕了。 :)
    • @DavidHammen 是的,你可以用其他方式来设置它的样式[这就是我通常编写自己的代码的方式],但可能不会(不让它变得很乱)把它减少到 2 行.. . 甚至四个合理可读的行都在推动它。所以声明仍然是“长了几行”。我的意思是让它更具可读性,而不是适合 IOCCC 条目。
    • +1。条件运算符在这里看起来不太好。我为此发布了另一个变体,它使用“短”条件运算符,使代码尽可能地可读/紧凑。这里有趣的是机器码是如何生成的。
    • 条件运算符的一个优点是它使编译器更有可能使用cmov 操作来消除代码中潜在的管道气泡。
    【解决方案4】:

    关于操作优先级和代码生成的有趣问题。

    好的,, 操作的优先级非常低(最低,请参阅reference table)。由于这个事实,您的代码与以下几行相同:

    ((x1<=x2) ? minx=x1,maxx=x2 : minx=x2),maxx=x1;
    ((y1<=y2) ? miny=y1,maxy=y2 : miny=y2),maxy=y1;
    

    实际上只有 C/C++ 语法可以防止第一个 , 出现相同的行为。

    C/C++ 运算优先级中另一个真正危险的地方是位运算和比较。考虑以下片段:

    int a = 2;
    int b = (a == 2|1); // Looks like check for expression result? Nope, results 1!
    

    展望未来,我建议以这种方式重写您的片段,以保持效率和可读性之间的平衡:

    minx = ((x1 <= x2) ? x1 : x2);
    maxx = ((x1 <= x2) ? x2 : x1);
    miny = ((y1 <= y2) ? y1 : y2);
    maxy = ((y1 <= y2) ? y2 : y1);
    

    关于这段代码最有趣的事实是,由于 CPU 指令集中的条件位标志,这种风格可能会为某些架构(如 ARM)生成最有效的代码(条件复制不会花费更多,将编译器指向正确的代码片段)。

    【讨论】:

      【解决方案5】:

      由于运算符优先级,表达式解析如下:

      (x1<=x2 ? minx=x1,maxx=x2 : minx=x2), maxx=x1;
      

      你可以解决这个问题:

      (x1<=x2) ? (minx=x1,maxx=x2) : (minx=x2, maxx=x1);
      

      实际上你不需要前两对括号。 Also check this question.

      【讨论】:

        【解决方案6】:

        因为运算符优先级:

        (x1&lt;=x2 ? minx=x1,maxx=x2 : minx=x2),maxx=x1

        您可以使用以下方法修复它:

        int x1=10, x2=20, y1=132, y2=12, minx, miny, maxx, maxy;
        x1<=x2 ? (minx=x1,maxx=x2) : (minx=x2,maxx=x1);
        y1<=y2 ? (miny=y1,maxy=y2) : (miny=y2,maxy=y1);
        cout<<"minx="<<minx<<"\n";
        cout<<"maxx="<<maxx<<"\n";
        cout<<"miny="<<miny<<"\n";
        cout<<"maxy="<<maxy<<"\n";
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-10-01
          • 2016-02-20
          • 1970-01-01
          • 1970-01-01
          • 2023-01-30
          • 1970-01-01
          相关资源
          最近更新 更多