只是添加另一种替代方法以避免条件执行(这不是我会使用的,但似乎从一组解决方案中丢失):
int max( int a, int b, int c ) {
int l1[] = { a, b };
int l2[] = { l1[ a<b ], c };
return l2[ l2[0] < c ];
}
该方法使用(与大多数其他方法一样),布尔表达式的结果在转换为 int 时会产生 0 或 1。两个值的简化版本是:
int max( int a, int b ) {
int lookup[] { a, b };
return lookup[ a < b ];
}
如果表达式a<b 正确,我们将返回b,小心地存储在查找数组的第一个索引中。如果表达式为 false,那么我们返回 a,它被存储为查找数组的元素 0。使用它作为构建块,您可以说:
int max( int a, int b, int c ) {
int lookup[ max(a,b), c ];
return lookup[ max(a,b) < c ];
}
可以通过使用已经存储在lookup[0] 中的结果避免第二次调用内部max 并内联对max(int,int) 的原始调用,将其简单地转换为上面的代码。
(这部分只是你在得出结论之前必须衡量的另一个证据,请参阅最后的编辑)
至于我将实际使用哪个...好吧,可能是@Foo Baa here 修改为使用内联函数而不是宏。下一个选项将是这个选项或@MSN here 提供的选项。
这三种解决方案在公认答案中没有出现的共同点是,它们不仅避免了if 或三元运算符?: 的语法结构,而且还避免了分支总的来说,这可能会对性能产生影响。在没有分支的情况下,CPU 中的 branch-predictor 不可能错过。
考虑性能时,先衡量再考虑
我实际上已经为 2-way max 实现了一些不同的选项,并分析了编译器生成的代码。以下三种解决方案生成所有相同的汇编代码:
int max( int a, int b ) { if ( a < b ) return b; else return a; }
int max( int a, int b ) { return (a < b? b : a ); }
int max( int a, int b ) {
(void)((a < b) && (a = b));
return a;
}
这并不奇怪,因为这三个都代表完全相同的操作。有趣的信息是生成的代码不包含任何分支。使用cmovge 指令实现很简单(在intel x64 平台上使用g++ 进行测试):
movl %edi, %eax # move a into the return value
cmpl %edi, %esi # compare a and b
cmovge %esi, %eax # if (b>a), move b into the return value
ret
诀窍在于条件移动指令,它可以避免任何潜在的分支。
其他解决方案都没有任何分支,但它们都转换为比其中任何一个更多的 cpu 指令,这最终让我们放心,我们应该始终编写简单的代码和让编译器为我们优化吧。