如果你想了解编译器在做什么,你需要查看一些汇编代码。我推荐这个网站(我已经输入了问题中的代码):
https://godbolt.org/g/FwZZOb。
第一个例子更有趣。
int div(unsigned int num, unsigned int num2) {
if( num >= num2 ) return num % num2;
return num;
}
int div2(unsigned int num, unsigned int num2) {
return num % num2;
}
生成:
div(unsigned int, unsigned int): # @div(unsigned int, unsigned int)
mov eax, edi
cmp eax, esi
jb .LBB0_2
xor edx, edx
div esi
mov eax, edx
.LBB0_2:
ret
div2(unsigned int, unsigned int): # @div2(unsigned int, unsigned int)
xor edx, edx
mov eax, edi
div esi
mov eax, edx
ret
基本上,编译器出于特定且合理的原因将不会优化该分支。如果整数除法与比较大致相同的代价,那么该分支就将是毫无意义的。但是整数除法(通常与模操作一起执行)实际上是非常昂贵的:http://www.agner.org/optimize/instruction_tables.pdf。这些数字在架构和整数大小方面差异很大,但通常的延迟时间可以从15个到接近100个周期。
通过在执行模操作之前进行分支,您实际上可以节省很多工作量。请注意:编译器在汇编级别也不会将没有分支的代码转换为分支。因为分支也有一个缺点:如果最终仍需要使用模操作,那么您浪费了一些时间。
在不知道 idx < idx_max
相对频率的情况下,无法进行合理的优化决策。因此,编译器(gcc 和 clang 做的一样)选择以相对透明的方式映射代码,将此选择交给开发人员处理。
因此,该分支可能是一个非常合理的选择。
第二个分支应该完全没有意义,因为比较和赋值的代价是相当的。尽管如此,您可以在链接中看到,如果编译器具有对变量的引用,它们仍将不执行此优化。如果值是局部变量(就像您演示的代码中那样),则编译器将优化该分支。
总之,第一段代码可能是一个合理的优化,而第二段代码可能只是一个疲惫的程序员。