【发布时间】:2022-01-09 00:34:15
【问题描述】:
为了更好地理解编译器,尤其是汇编语言,我一直在试验一段简单的代码,其中计算了第一个 N 数字的总和,结果应该是 N(N+1)/2 或 N(N-1)/2。
如代码所示,有两个功能:
#include <cstdint>
// Once compiled with optimization, the generated assembly has a loop
uint64_t sum1( uint64_t n ) {
uint64_t sum = 0;
for ( uint64_t j=0; j<=n; ++j ) {
sum += j;
}
return sum;
}
// Once compiled with optimization, the generated assembly of the following has no loop
uint64_t sum2( uint64_t n ) {
uint64_t sum = 0;
for ( uint64_t j=0; j<n; ++j ) {
sum += j;
}
return sum;
}
在第一个函数中,我从 O 循环到 N,即j<=n,在第二个函数中,我从 O 到 N-1,即j<n。
我的理解/观察:
-
对于第一个函数
sum1,生成的程序集有一个循环,而对于第二个函数sum2,程序集没有循环。但是,一旦我删除了编译器优化,即-O3,那么你终于可以看到汇编中第二个函数的循环了。 -
要查看编译器优化后生成的程序集,请参阅Optimized。
-
要查看未经编译器优化的生成程序集,请参阅此non-optimized。
-
编译器是 x86-64 clang
问题:为什么编译器优化不显示程序集中的另一个循环?
【问题讨论】:
-
编译器似乎观察到
sum1可以永远循环,对于某个n。 -
编译器的(理想)目标是创建最高效的代码,其具有与源代码相同的可观察行为(优化时)。在循环中添加 temp 的输出以强制编译器保持循环。
-
考虑
sum1(std::numeric_limits<uint64_t >::max()) -
@SamVarshavchik:正如 user17732522 所指出的,编译器可能会假设循环终止;这是 C 或 C++ 标准授予的公理。这在逻辑上等同于假设
n!=UINT64_MAX,因为这些都是循环终止的所有情况和唯一情况。所以这相当于说如果程序以n等于UINT64_MAX执行,则行为不是由C 或C++ 标准定义的。它没有被定义为无限循环。 -
@IgorTandetnik:这仅适用于 C++。在 C 中,它并不适用于所有循环;具有控制表达式的迭代语句是常量表达式(包括不存在的表达式,它隐式非零)可以无限继续而没有副作用。因此,您可以编写 OP 的循环并通过将
j <= n从控制表达式移动到条件break;来获得定义的无限循环行为而不是未定义的行为。
标签: c++ loops assembly clang compiler-optimization