我想看看GCC是否会使用有符号和无符号整数将a - (b - c)
减少为(a + c) - b
,因此我创建了两个测试
//test1.c
unsigned fooau(unsigned a, unsigned b, unsigned c) { return a - (b - c); }
signed fooas(signed a, signed b, signed c) { return a - (b - c); }
signed fooms(signed a) { return a*a*a*a*a*a; }
unsigned foomu(unsigned a) { return a*a*a*a*a*a; }
//test2.c
unsigned fooau(unsigned a, unsigned b, unsigned c) { return (a + c) - b; }
signed fooas(signed a, signed b, signed c) { return (a + c) - b; }
signed fooms(signed a) { return (a*a*a)*(a*a*a); }
unsigned foomu(unsigned a) { return (a*a*a)*(a*a*a); }
我先用gcc -O3 test1.c test2.c -S
编译并查看了程序集。对于这两个测试fooau
是相同的,但fooas
不是。
据我所知,无符号算术可以从the following formula
派生(a%n + b%n)%n = (a+b)%n
可用于表明无符号算术是关联的。但是由于signed overflow is undefined behavior这种相等不一定适用于有符号加法(即有符号加法不是关联的),这解释了为什么GCC没有将a - (b - c)
减少到(a + c) - b
的有符号整数。但是我们可以告诉GCC使用-fwrapv
来使用这个公式。对两个测试使用此选项fooas
是相同的。
但乘法怎么样?对于这两个测试,fooms
和foomu
被简化为三次乘法(a*a*a*a*a*a to (a*a*a)*(a*a*a)
)。但乘法可以写成重复加法,所以使用上面的公式我认为可以显示
((a%n)*(b%n))%n = (a*b)%n
我认为也可以表明无符号模乘也是关联的。但由于GCC仅对foomu
使用了三次乘法,这表明GCC假设有符号整数乘法是关联的。
这似乎与我相矛盾。对于加法,有符号算术不是关联的,但对于乘法,它是。
两个问题:
添加是否与有符号整数无关,但乘法是否在C / C ++中?
如果使用带符号的溢出来进行优化,那么GCC没有减少代数表达式的事实是不能优化的吗?使用-fwrapv
进行优化不会更好(我知道a - (b - c)
到(a + c) - b
并没有太大的减少,但我担心更复杂的情况) ?这是否意味着优化有时候使用-fwrapv
会更有效率,有时候不是吗?
答案 0 :(得分:6)
不,乘法在有符号整数中不是关联的。考虑(0 * x) * x
与0 * (x * x)
- 后者有可能未定义的行为,而前者总是被定义。
未定义行为的可能性只会引入新的优化机会,典型的例子是为签名x + 1 > x
优化true
到x
,无符号整数的优化。
我认为您不能认为gcc未能将a - (b - c)
更改为(a + c) - b
代表错失的优化机会;这两个计算在x86-64(leal
和subl
)上编译为相同的两个指令,只是顺序不同。
实际上,实现有权假设算术是关联的,并将其用于优化,因为任何事情都可能发生在UB上,包括模运算或无限范围算术。但是, you 作为程序员无权假设关联性,除非您能保证没有中间结果溢出。
另一个示例,请尝试(a + a) - a
- gcc会针对已签名的a
以及未签名的a
将其优化为 public ImageSource selectedImage
{
ImageSource temp;
get{
if (selected)
{
temp=new BitmapImage(new Uri("ms-appx:////Assets/ic_selected.png", UriKind.RelativeOrAbsolute));
return temp ;
}
else
{
temp=new BitmapImage(new Uri("/Assets/ic_not_selected.png", UriKind.RelativeOrAbsolute));
return temp ;
}
}
}
。
答案 1 :(得分:2)
如果对任何定义的输入集具有相同的结果,则可以执行有符号整数表达式的代数减少。所以如果表达式
a * a * a * a * a * a
定义 - 也就是说,a
足够小,在计算过程中不会发生符号溢出 - 然后任何重新组合将产生相同的值,因为没有少于6的乘积{{1 s可以溢出。
a
也是如此。
如果变量乘以(或添加)的变量不完全相同,或者加法与减法混合,则情况会发生变化。在这些情况下,重新组合和重新安排计算可能会导致签名溢出,这在规范计算中不会发生。
例如,采用表达式
a + a + a + a + a + a
代数地,这相当于
a - (b - c)
但编译器无法进行重新排列,因为中间值(a + c) - b
可能会溢出输入,这不会导致原始溢出。假设我们有a+c
然后a=INT_MAX-1; b=1; c=2;
导致溢出,但a+c
计算为a - (b - c)
,即a - (-1)
,没有溢出。
如果编译器可以假设有符号溢出没有陷阱,而是以模INT_MAX
为模计算,那么这些重新排列是可能的。 INT_MAX+1
选项允许gcc做出这样的假设。