中国开发网: 论坛: 程序员情感CBD: 贴子 582123
haitao
哈哈,好像我又猜对了!——GCC把:-10 * abs( i - 1 ) 和 10 * abs( i - 1 )优化成了:abs( -10 * i + 10 ) 和 abs( 10……
GCC的BUG研究(Rev.3)
http://blog.csdn.net/Raptor/archive/2007/11/19/1893079.aspx

Solidot报道GCC在Linux平台下有一个BUG。但是原文中说只有Linux平台有这个问题是不正确的,经过令狐的实际测试,在HP-UX(GCC 4.0.2),LINUX(UBUNTU,GCC 4.1.2),WINDOWS(GCC 3.4.5)下都存在在这个问题。

为了调查研究一下这个问题究竟是如何造成的,我们一帮人展开了一番讨论,经过对汇编代码的分析,结果看来是GCC的代码优化实现有问题。

测试的C源程序如下:

int main () { int i=2; if( -10*abs (i-1) == 10*abs(i-1) ) printf ("OMG,-10==10 in linux!\n"); else printf ("nothing special here\n");}
在X86 Linux平台下的汇编代码片段如下:

19 mov DWORD PTR [%ebp-8], 220 mov %edx, DWORD PTR [%ebp-8]21 mov %eax, %edx22 sal %eax, 223 add %eax, %edx24 add %eax, %eax25 sub %eax, 1026 mov %edx, %eax27 sar %edx, 3128 mov %ecx, %edx29 xor %ecx, %eax30 sub %ecx, %edx31 mov %eax, DWORD PTR [%ebp-8]32 imul %eax, %eax, -1033 add %eax, 1034 mov %edx, %eax35 sar %edx, 3136 xor %eax, %edx37 sub %eax, %edx38 cmp %ecx, %eax39 jne .L2
(完整的代码在这里:X86 Linux,HP-UX——由令狐虫友情提供)

代码并不难理解:

其中 DWORD PTR [%ebp-8] 就是变量 i 。21行到25行之间是计算 i * 10 - 10 ,结果保存在 eax 中。26行到30行是 abs() 函数的实现,我以前对这个倒还真没研究过,现在一看之下发现这个实现还真是有创意啊(回头细说)。31行到33行是计算 i * -10 + 10 ,结果保存在 eax 中。34行到37行同样是 abs() 函数的实现。38行到39行是比较跳转。

所以结果已经很清楚了,GCC把:

-10 * abs( i - 1 ) 和 10 * abs( i - 1 )
优化成了:

abs( -10 * i + 10 ) 和 abs( 10 * i - 10 )
结果当然就不正确了。

结论就是:不是 abs() 函数实现的问题,也不是代码解析的问题,是优化的问题——编译器最容易出问题的地方就是优化。

现在最不能理解的就是:这样做并不见得更“优”,何必要这样“优化”呢?

补充(Rev.2):

后来经三火指点,这种优化被称为Const Foldering,也就是说把常量提取出来在编译时计算,以优化运行时的性能。本来对于取绝对值这种情况是不应该这么做的,因为 abs() 本身是一个函数,但是与令狐讨论后,他认为GCC在这里实际上是把它优化为一个操作,所以同时对它进行了Const Foldering。

关于在这个Const Foldering的BUG,有人提供了一个补丁:《[PATCH] Fix PR34130, extract_muldiv broken》,其中的修正代码是这一段:

*** fold-const.c (revision 130238)--- fold-const.c (working copy)*************** extract_muldiv_1 (tree t, tree c, enum t*** 6095,6100 ****--- 6095,6103 ---- } break; }+ /* If the constant is negative, we cannot simplify this. */+ if (tree_int_cst_sgn (c) == -1)+ break; /* FALLTHROUGH */ case NEGATE_EXPR: if ((t1 = extract_muldiv (op0, c, code, wide_type, strict_overflow_p))
在这个补丁代码里,是简单地对要处理的常量进行判断,如果是负数就跳过Const Foldering优化部分。但我和令狐都认为,这种解决方案只是一种权宜之计,是一种明显的坏味道。

我觉得根本的解决方案是将 abs() 从Const Foldering优化的操作列表中去除——但是将 abs() 优化为一个操作的确是很有用的,如果Const Foldering是对所有操作都进行优化的话,这种修改也可能会带来别的坏味道。我不知道GCC中还把什么函数优化为操作,但是如果是对所有操作进行 Const Foldering的话,潜在风险还会有的,因为Const Foldering只对线性函数正确,而 abs() 出问题正是因为它是一个非线性函数。

补充(Rev.3):

关于优化选项的问题,我们刚才又做了一下研究。使用 -O0 选项关闭优化,仍然生成与无优化选项类似的代码,看来这种是属于默认优化的部分。使用 -O1 和 -O2 选项生成的目标代码很相似,都是高度优化的(但结果仍然是错误的):

mov DWORD PTR [esp], OFFSET FLAT:LC0 call _puts
(完整的代码在这里:X86 Linux——由Mike友情提供)

可见只剩下一句 printf("OMG..."); 。这也就意味着这个最优化代码是在Const Foldering 之后,编译器又发现了 i 本质上也是一个常量,所以优化成了现在这个样子。如果编译器是先发现 i 是常量,再作Const Foldering 的话,结果就会是正确的了——令狐昨天已经试过,把 i - 1 换成 1 以后,默认优化生成的代码与现在最优化生成的代码差不多,只不过输出结果是正确的 printf("nothing..."); 。


====附录的分割线====


附一段关于这个 abs() 函数实现的说明:

整数取绝对值的方法基本上就是判断是否小于0,如果是则取负,否则直接返回。GCC里的实现则比较巧妙,没有判断跳转的过程(我猜测是基于CPU运行优化考虑)。它的做法是用 sar 指令填充符号位得到一个数,对于正数,这个符号数为0,对于负数,这个符号数为全1(即-1)。然后用这个符号数与原数异或,如果是正数将不变(与0异或 不变),如果是负数将取反(与1异或取反)。最后将异或结果减符号数,对于正数来说,减0原值仍然不变,对于负数来说,减-1相当于+1——在补码中,一 个整数取反加1的结果正是等于对其取负。于是实现了绝对值的计算。

要汗一下的是,我今天刚在豆瓣上跟人说:不做底层工作不需要了解汇编。结果这就碰到一个反例。





Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1893079



[收藏到我的网摘] [发送Trackback] 猛禽发表于 2007年11月19日 17:29:00




特别推荐:想在这里投放广告?点击查看详情
[分享]未来开发趋势!
四大主题 Web开发2.0 企业开发2.0 系统开发2.0 语言工具2.0
设计模式的应用与积累
了解如何在实际开发中运用 设计模式和积累自己的模式
Oracle 企业管理器下载
进入Oracle最强大的在线支持系统
2007年SOA发展趋势的分析和建议
越来越多使用Java 和.NET技术平台的数据中心 SOA内及其相关行业将出现如下趋势,并向您提供这样
基于Eclipse的下一代建模工具
提高软件开发效率 IBM Rational

| 下一篇: 不用模式的理由


评论
# fangzhe 发表于2007-11-19 18:41:25 IP: 222.69.215.*
甚至还危害到PowerPC的

……
mulli r2,r0,-10
addi r0,r2,10
srawi r2,r0,31
xor r9,r2,r0
subf r9,r2,r9
lwz r0,56(r30)
mulli r2,r0,10
addi r0,r2,-10
srawi r2,r0,31
xor r0,r2,r0
subf r0,r2,r0
cmpw cr7,r9,r0
bne cr7,L2
……

和x86一致。还有个有趣的现象,如果编译时打开-O2,连L2这个分支都优化到不存在。
显然是语法解析(tree-xxx.c)那里出的问题。
gcc-4.2



# Raptor 发表于2007-11-19 23:10:44 IP: 222.64.26.*
O2优化我们还没研究,明天再研究看看。
我们只试了,如果把i-1换成1的话,整个if判断和L1部分会被完全优化掉,直接到L2——因为没有了abs(),这样结果反而是正确的。
我的blog:http://szhaitao.blog.hexun.com & http://www.hoolee.com/user/haitao
--以上均为泛泛之谈--
不尽牛人滚滚来,无边硬伤纷纷现 人在江湖(出来的),哪能不挨刀(总归是要的)
网络对话,歧义纷生;你以为明白了对方的话,其实呢?

您所在的IP暂时不能使用低版本的QQ,请到:http://im.qq.com/下载安装最新版的QQ,感谢您对QQ的支持和使用

相关信息:


欢迎光临本社区,您还没有登录,不能发贴子。请在 这里登录