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], 2
20 mov %edx, DWORD PTR [%ebp-8]
21 mov %eax, %edx
22 sal %eax, 2
23 add %eax, %edx
24 add %eax, %eax
25 sub %eax, 10
26 mov %edx, %eax
27 sar %edx, 31
28 mov %ecx, %edx
29 xor %ecx, %eax
30 sub %ecx, %edx
31 mov %eax, DWORD PTR [%ebp-8]
32 imul %eax, %eax, -10
33 add %eax, 10
34 mov %edx, %eax
35 sar %edx, 31
36 xor %eax, %edx
37 sub %eax, %edx
38 cmp %ecx, %eax
39 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的结果正是等于对其取负。于是实现了绝对值的计算。
要汗一下的是,我今天刚在豆瓣上跟人说:不做底层工作不需要了解汇编。结果这就碰到一个反例。
分享到:
相关推荐
scratch少儿编程逻辑思维游戏源码-城堡战争.zip
内容概要:本文档汇集了来自字节跳动、腾讯、金山WPS、跟谁学和百度等大厂的Go工程师面试题,涵盖广泛的技术领域。主要包括Go语言特性(如goroutine调度、channel机制)、操作系统(进程间通信、线程调度)、计算机网络(TCP/IP协议栈、HTTP协议)、数据结构与算法(排序算法、LRU缓存)、数据库(MySQL索引优化、Redis内部机制)、分布式系统(负载均衡、服务发现)等方面的知识点。通过这些问题,不仅考察应聘者的理论基础,还测试其实际项目经验和技术深度。 适合人群:有一定Go语言编程经验和计算机基础知识的开发者,特别是准备应聘互联网大厂的中级及以上水平的后端工程师或全栈工程师。 使用场景及目标:①帮助求职者全面复习Go语言及其相关领域的核心概念;②为面试官提供有价值的参考题目,确保候选人具备解决复杂问题的能力;③指导工程师深入理解并掌握企业级应用开发所需的关键技能。 阅读建议:由于题目覆盖面广且难度较高,建议读者结合自身情况选择重点复习方向,同时配合实际编码练习加深理解。对于每个知识点,不仅要记住答案,更要理解背后的原理,这样才能在面试中灵活应对各种变体问题。
scratch少儿编程逻辑思维游戏源码-堡垒之夜(吃鸡游戏).zip
少儿编程scratch项目源代码文件案例素材-派.zip
scratch少儿编程逻辑思维游戏源码-Scratch 冒险.zip
2025 飞特舵机, Arduino版本
scratch少儿编程逻辑思维游戏源码-躲避.zip
内容概要:本文详细介绍了利用PFC5.0进行纤维混凝土三点弯曲模拟的方法。首先,作者展示了如何通过定义纤维的体积含量、长度、半径和刚度等关键参数来构建纤维网络。接着,描述了三点弯曲加载的具体实现方式,包括加载速率控制和终止条件设定。最后,提供了后处理方法,如绘制并导出力-位移曲线图,以便于分析材料破坏机制。文中还给出了若干实用建议,如纤维半径的选择范围、加载速率的初始值以及不同类型纤维的接触模型选择。 适合人群:从事材料科学尤其是混凝土材料研究的专业人士,以及对离散元法和数值模拟感兴趣的科研工作者。 使用场景及目标:适用于希望深入了解纤维混凝土力学性能的研究人员,旨在帮助他们掌握PFC5.0软件的操作技巧,优化模拟参数设置,提高实验效率。 其他说明:文中提供的代码片段可以直接应用于实际项目中,同时附带了一些实践经验分享,有助于初学者快速入门并避免常见错误。
少儿编程scratch项目源代码文件案例素材-生存V1(有BAG).zip
少儿编程scratch项目源代码文件案例素材-披萨机器人.zip
少儿编程scratch项目源代码文件案例素材-气球滑雪板.zip
少儿编程scratch项目源代码文件案例素材-使命召唤(苏联插旗).zip
1. GPIO模拟I2C 实战项目,根据正点原子 STM32F407ZGT6 进行更改; 2. 可适配STM32、GD32、HC32等MCU;
scratch少儿编程逻辑思维游戏源码-百米冲刺.zip
内容概要:本文档汇总了蓝桥杯历年试题及练习资源,涵盖编程类试题精选、硬件与单片机试题、练习资源与题库以及备考建议。编程类试题精选包括基础算法题(如数组求和、质因数分解)、经典算法案例(如最大子序列和、兰顿蚂蚁模拟)和数据结构应用(如字符全排列)。硬件与单片机试题主要涉及客观题考点,如BUCK电路和电源设计。练习资源与题库部分介绍了真题平台(如Dotcpp、CSDN专题)和专项训练包(如Python题库、Java百题集、C++真题解析)。备考建议分为分阶段练习(新手阶段、进阶提升)和模拟实战(如使用Dotcpp估分系统进行限时训练),强调按年份和组别分类练习,强化代码实现与调试能力。; 适合人群:准备参加蓝桥杯竞赛的学生及编程爱好者。; 使用场景及目标:①针对不同编程语言和难度级别的题目进行专项训练;②通过历年真题和模拟实战提高解题速度和准确性;③掌握算法设计、数据结构应用及硬件基础知识。; 阅读建议:此文档提供了丰富的试题和练习资源,建议根据自身水平选择合适的题目进行练习,并结合真题平台的估分系统和社区开源代码进行对比优化,逐步提升编程能力和竞赛水平。
内容概要:本文详细介绍了30kW储能PCS(电力转换系统)原理图的设计要点及其量产化过程中需要注意的技术细节。首先阐述了储能PCS的基本概念和重要性,接着深入探讨了主拓扑结构的选择,特别是双级式结构的优势以及关键组件如IGBT的驱动时序配置。随后讨论了控制算法的智能化改进,包括加入前馈补偿以提高系统的稳定性。此外,还强调了EMC设计、PCB布局、元件选择等方面的注意事项,并分享了一些实际生产中遇到的问题及解决方案。最后提到了自动化测试方法和散热管理策略,确保产品在各种环境下的可靠运行。 适合人群:从事储能系统设计、电力电子产品研发的工程师和技术人员。 使用场景及目标:帮助读者掌握30kW储能PCS从原理图设计到量产实施的全流程关键技术,提升产品的性能和可靠性,避免常见错误。 其他说明:文中提供了具体的代码片段和实践经验,有助于理解和应用相关理论。
少儿编程scratch项目源代码文件案例素材-喷气包多德.zip
内容概要:本文深入探讨了齿轮啮合性能及其动态特性,特别是直齿轮的基础参数计算、渐开线绘制以及接触力仿真的具体实现。首先介绍了齿轮的基本参数如模数、齿数、压力角等,并给出了具体的计算实例。接着详细讲解了如何利用Python进行渐开线的数学建模并绘图展示,强调了这种曲线对于确保齿轮平稳传动的重要性。然后讨论了齿轮在啮合过程中接触力的变化规律,提供了简化的Python代码来模拟这一现象。最后指出,在实际工程项目中应当借助专业的软件包如PyDy或ADAMS来进行更加精确的动力学分析,同时肯定了自行编写代码的价值在于能够更好地理解和排查问题。 适合人群:机械工程领域的研究人员、工程师以及相关专业的学生。 使用场景及目标:①帮助读者掌握齿轮基本理论知识;②指导读者运用Python编程技能完成简单的齿轮性能分析任务;③为后续深入研究提供思路和技术支持。 阅读建议:由于文中涉及较多的专业术语和数学公式,建议读者提前复习相关基础知识,并尝试运行提供的代码片段加深理解。此外,对于想要进一步探索该领域的读者来说,可以参考文末提到的专业工具包进行更复杂的研究。
少儿编程scratch项目源代码文件案例素材-任务.zip
少儿编程scratch项目源代码文件案例素材-时光大盗.zip