我想看看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
不是。
据我所知,无符号算术可以从下面的公式中推导出来
(a%n + b%n)%n = (a+b)%n
其可用于表明无符号算术是关联的。但是,由于有符号溢出是未定义的行为,这种等式不一定适用于有符号加法(即,有符号加法不是关联的(,这解释了为什么GCC没有将有符号整数的a - (b - c)
减少到(a + c) - b
。但我们可以用-fwrapv
告诉GCC使用这个公式。将此选项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 * x) * x
与0 * (x * x)
——后者具有潜在的未定义行为,而前者总是被定义的。 -
未定义行为的潜力只会带来新的优化机会,经典的例子是为有符号
x
优化x + 1 > x
到true
,这是一种不适用于无符号整数的优化。
我不认为你可以假设gcc未能将a - (b - c)
更改为(a + c) - b
代表错过了优化机会;这两个计算以不同的顺序编译为x86-64上相同的两条指令(leal
和subl
(。
事实上,实现有权假设算术是关联的,并将其用于优化,因为UB上可能发生任何事情,包括模算术或无限范围算术。然而,您作为程序员无权假设关联性,除非您能够保证没有中间结果溢出。
作为另一个示例,try (a + a) - a
-gcc将对有符号a
和无符号a
进行优化。
只要对任何定义的输入集具有相同的结果,就可以执行有符号整数表达式的代数约简。因此,如果表达式
a * a * a * a * a * a
定义——也就是说,a
足够小,在计算过程中不会发生有符号溢出——那么乘法的任何重新分组都将产生相同的值,因为少于六个a
的乘积都不会溢出。
CCD_ 32也是如此。
如果相乘(或相加(的变量不完全相同,或者加法与减法混合,情况就会发生变化。在这些情况下,重新组合和重新排列计算可能会导致有符号溢出,而这在规范计算中不会发生。
例如,以表达式为例
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
为模计算的,那么这些重排是可能的。-fwrapv
选项允许gcc做出这种假设。