这是UB;用ISO C ++术语来说,整个程序的整个行为是完全不确定的,因为它最终会击中UB。就C ++标准而言,最经典的例子是它可以使恶魔从你的鼻子里飞出来。(我建议您不要使用可能存在鼻恶魔的实现方式)。请参阅其他答案以获取更多详细信息。
编译器可能会在编译时为执行路径“造成麻烦”,从而看到编译路径可见的UB,例如,假定从未到达那些基本块。
另请参见每个C程序员应了解的未定义行为(LLVM博客)。如此处所述,带符号溢出的UB可使编译器证明for(... i <= n ...)
循环不是无限循环,即使对于未知循环也是如此n
。它还允许他们将int循环计数器“提升”为指针宽度,而不是重做符号扩展。(因此,在这种情况下,如果您希望将UB的带符号包装i
放入其值范围内,则UB的结果可能是访问数组的低64k或4G元素之外。)
在某些情况下,编译器将ud2
针对某个块发出x86之类的非法指令,如果执行了该块,可证明会导致UB。(请注意,功能可能不会永远被调用,所以编译器一般不能发狂,并通过不打UB功能突破等功能,甚至可能路径。即机器代码,它编译成仍须工作所有不会导致UB的输入。)
可能最有效的解决方案是手动剥离最后一次迭代,以便factor*=10
避免不必要的操作。
int result = 0;
int factor = 1;
for (... i < n-1) { // stop 1 iteration early
result = ...
factor *= 10;
}
result = ... // another copy of the loop body, using the last factor
// factor *= 10; // and optimize away this dead operation.
return result;
或者,如果循环体很大,请考虑简单地为使用无符号类型factor
。 然后,您可以让无符号乘法溢出,它将进行定义明确的换行以将2的幂(无符号类型中的值位数)进行包装。
即使您将其与带符号的类型一起使用,也没关系,特别是在您的unsigned-> signed转换永远不会溢出的情况下。
无符号和2的补码之间的转换是免费的(所有值都使用相同的位模式);由C ++标准指定的int-> unsigned的模数包装简化为仅使用相同的位模式,而不像一个人的补码或符号/幅度。
和unsigned-> signed相似,尽管它是由实现定义的,用于大于的值,但同样也很简单INT_MAX
。如果您没有使用上一次迭代产生的巨大的无符号结果,则无需担心。但是,如果是这样,请参阅从未签名转换为未定义符号吗?。值不适合的情况是实现定义的,这意味着实现必须选择一些行为。那些理智的人只需截断(如果需要)无符号的位模式并将其用作带符号的,因为这对于范围内的值以相同的方式起作用,而无需额外的工作。而且绝对不是UB。因此,大的无符号值可以成为负有符号整数。例如,在int x = u;
gcc和clang不能优化之后x>=0
一如既往地真实,即使没有-fwrapv
,因为它们定义了行为。