得到了x?y:z表达式的意外答案

cao*_*lei 27 c++ ternary-operator operator-precedence comma-operator c++11

这是一个简单的C++代码段:

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";
Run Code Online (Sandbox Code Playgroud)

我认为结果应该是:

minx=10
maxx=20
miny=12
maxy=132
Run Code Online (Sandbox Code Playgroud)

但实际上结果是:

minx=10
maxx=10
miny=12
maxy=132
Run Code Online (Sandbox Code Playgroud)

有人可以解释为什么maxx不是20?谢谢.

per*_*eal 40

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

(x1<=x2 ? minx=x1,maxx=x2 : minx=x2), maxx=x1;
Run Code Online (Sandbox Code Playgroud)

你可以解决这个问题:

(x1<=x2) ? (minx=x1,maxx=x2) : (minx=x2, maxx=x1);
Run Code Online (Sandbox Code Playgroud)

实际上你不需要前两对括号.还要检查这个问题.


Dan*_*her 25

条件运算符的优先级大于逗号运算符的优先级,因此

x1<=x2 ? minx=x1,maxx=x2 : minx=x2,maxx=x1;
Run Code Online (Sandbox Code Playgroud)

括号括起来

(x1<=x2 ? minx=x1,maxx=x2 : minx=x2),maxx=x1;
Run Code Online (Sandbox Code Playgroud)

因此,无论条件如何,最后的分配都完成.

要解决它,你可以

使用或不使用优化进行编译时,if版本和带括号的括号单个条件在gcc(4.7.2)和clang(3.2)下生成相同的程序集,从其他编译器也可以合理地预期.具有两个条件的版本产生不同的程序集,但是通过优化,这两个编译器也只发出一条cmp指令.

在我看来,if版本是最容易验证的正确性,所以更可取.


Mat*_*son 5

虽然其他人已经解释了问题的原因是什么,但我认为"更好"的解决方案应该是在以下情况下编写条件:

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;
}
Run Code Online (Sandbox Code Playgroud)

是的,它有几行更长,但它也更容易阅读并清楚确切地发生了什么(如果你需要在调试器中单步执行它,你可以很容易地看到它的方式).

任何现代编译器都应该能够将这些中的任何一个转换为相当有效的条件赋值,这些条件赋值可以很好地避免分支(因此"坏分支预测").

我准备了一个小测试,我编译使用

g++ -O2 -fno-inline -S -Wall ifs.cpp
Run Code Online (Sandbox Code Playgroud)

这是源代码(我必须使它成为参数,以确保编译器不仅直接计算正确的值而且只是做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;
}
Run Code Online (Sandbox Code Playgroud)

生成的代码如下所示:

_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.... 
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,mine并且original是相同的,并且romano使用稍微不同的寄存器和不同形式的cmov,但是否则它们在相同数量的指令中执行相同的操作.