最近我讨论了一个想要检查签名int溢出的人,如if (A + B < 2 * max(A, B))
。让我们暂时忽略逻辑本身是错误的,并在C / C ++的上下文中讨论有符号整数溢出。 (我认为完全从C继承了这部分标准)。
什么样的需要签名整数溢出的检查将被当前的GCC优化掉,哪个不会?
由于原始文本并非完整,并且显然存在争议,我决定稍微改变一下这个问题,但请保留原文。
下面使用的所有示例都经过了gcc版本4.7.2 (Debian 4.7.2-5)
的测试,并使用-O3
进行了编译
即,它是未定义的,GCC臭名昭着地使用它来执行一些分支简化。想到的第一个例子就是
int i = 1;
while (i > 0){
i *= 2;
}
产生无限循环。这种优化的另一种情况是
if (A + 2 < A){
/* Handle potential overflow */
}
其中,假设A
是有符号整数类型,溢出分支将被完全删除。
更有趣的是,一些容易可证明的整数溢出的情况保持不变,例如
if (INT_MAX + 1 < 0){
/* You wouldn't write this explicitly, but after static analysis the program
could be shown to contain something like this. */
}
它会触发您期望的两个补码表示的分支。同样,此代码使条件分支保持不变
int C = abs(A);
if (A + C < 0){
/* For this to be hit, overflow or underflow had to happen. */
}
现在问题是,是否有一种看起来大致类似于if (A + B < C)
或if (A + B < c)
的模式,它会被优化掉?当我在写这篇文章之前谷歌搜索时,似乎最后的片段应该被优化掉了,但是我无法在溢出检查中重现这种错误,这种错误不会明确地与常量一起运行。
答案 0 :(得分:4)
许多编译器将使用&#34; false&#34;替换涉及有符号整数或指针的表达式,如
a + 1 < a // signed integer a
p + 1 < p // Pointer p
表达式只能在未定义行为的情况下为true。另一方面,这允许
for (char* q = p; q < p + 2; ++q) ...
要内联,用q = p和q = p + 1代替,不做任何检查,这样做是件好事。
if (A + abs (A) < 0)
对于许多编译器来说可能太复杂了。请注意,对于无符号整数,没有未定义的行为。因此,使用带有64位指针的无符号32位整数的循环往往比必要的慢,因为必须考虑环绕行为。对于无符号32位整数和64位指针,可能是
&p [i] > &p [i+1]
没有未定义的行为(不是64位整数或32位指针)。
答案 1 :(得分:1)
如果我可以解释你的问题,我相信这是在问这样的事情。
是否存在一个编译器如此积极地优化有符号整数表达式,以便它准备对这些表达式的某些类别进行详细分析,以便确定整个range of representable values for the type
中依赖条件为真(或错误)表达式的结果,并通过这些方法删除条件测试?
您提供的编译器是GCC的特定版本,并且您提供的表达式属于窄范围,但我认为您还有兴趣了解其他编译器或密切相关的表达式。
答案是现在我不知道一个,但这可能只是时间问题。
现有编译器对包含常量或某些可识别模式的表达式执行过早评估,如果在此评估期间遇到未定义的行为,通常会避免优化表达式。他们没有义务这样做。
数据流分析是CPU和内存密集型的,并且往往在有大量收益的地方使用。最终,C ++标准将停止更改(如此多),编译器编写者将有时间在他们手中。当编译器读取素数筛子程序并将其优化为单个打印语句时,我们仍然有点不足,但它会来。
我的回答主要是指出这实际上是一个关于编译器技术的问题,与C ++标准没什么关系。也许你应该直接向GCC小组询问。