C / C ++中有符号整数表达式的代数约简

时间:2015-05-21 12:25:33

标签: c++ c gcc optimization integer

我想看看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是相同的。

但乘法怎么样?对于这两个测试,foomsfoomu被简化为三次乘法(a*a*a*a*a*a to (a*a*a)*(a*a*a))。但乘法可以写成重复加法,所以使用上面的公式我认为可以显示

((a%n)*(b%n))%n = (a*b)%n
我认为

也可以表明无符号模乘也是关联的。但由于GCC仅对foomu使用了三次乘法,这表明GCC假设有符号整数乘法是关联的。

这似乎与我相矛盾。对于加法,有符号算术不是关联的,但对于乘法,它是。

两个问题:

  1. 添加是否与有符号整数无关,但乘法是否在C / C ++中?

  2. 如果使用带符号的溢出来进行优化,那么GCC没有减少代数表达式的事实是不能优化的吗?使用-fwrapv进行优化不会更好(我知道a - (b - c)(a + c) - b并没有太大的减少,但我担心更复杂的情况) ?这是否意味着优化有时候使用-fwrapv会更有效率,有时候不是吗?

2 个答案:

答案 0 :(得分:6)

  1. 不,乘法在有符号整数中不是关联的。考虑(0 * x) * x0 * (x * x) - 后者有可能未定义的行为,而前者总是被定义。

  2. 未定义行为的可能性只会引入新的优化机会,典型的例子是为签名x + 1 > x优化truex,无符号整数的优化。

  3. 我认为您不能认为gcc未能将a - (b - c)更改为(a + c) - b代表错失的优化机会;这两个计算在x86-64(lealsubl)上编译为相同的两个指令,只是顺序不同。

    实际上,实现有权假设算术是关联的,并将其用于优化,因为任何事情都可能发生在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做出这样的假设。