下面是一个简单的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";
我认为结果应该是:
minx=10
maxx=20
miny=12
maxy=132
但实际上结果是:
minx=10
maxx=10
miny=12
maxy=132
谁能解释一下为什么maxx
不是20
?谢谢。
由于运算符优先,表达式解析如下:
(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;
被括号括为
(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
版本是最容易验证正确性的,所以更可取
虽然其他人已经解释了问题的原因,但我认为"更好"的解决方案应该是用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....
可以看到,mine
和original
是相同的,romano
使用稍微不同的寄存器和cmov
的不同形式,但除此之外,它们在相同数量的指令中做同样的事情。
关于操作优先级和代码生成的有趣问题
OK, ,
操作的优先级非常低(最低,参见参考表)。由于这个事实,您的代码与以下几行相同:
((x1<=x2) ? minx=x1,maxx=x2 : minx=x2),maxx=x1;
((y1<=y2) ? miny=y1,maxy=y2 : miny=y2),maxy=y1;
实际上只有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。
由于运算符优先级:
(x1<=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";
在c++ 11中,您可以使用std::tie
和std::make_pair
来制作这个显而易见的正确-at-a-glance (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";
}
在线输出。
这在语义上等同于所有其他发布的解决方案,并且使用任何体面的优化编译器,没有任何开销。语法上更好,因为它有
- 最小代码重复量
- 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";
}
在线输出。