【转】C 编译器优化过程中的 Bug
C 编译器优化过程中的 Bug
一个朋友向我指出一个最近他们发现的 GCC 编译器优化过程(加上 -O3 选项)里的 bug,导致他们的产品出现非常诡异的行为。这使我想起以前见过的一个 GCC bug。当时很多人死活认为那种做法是正确的,跟他们说不清楚。简言之,这种有问题的优化,喜欢利用 C 语言的“未定义行为”(undefined behavior)进行推断,最后得到奇怪的结果。
这类优化过程的推理方式都很类似,他们使用一种看似严密而巧妙的推理,例如:“现在有一个整数 x
,我们不知道它是多少。但 x
出现在一个条件语句里面,如果 x > 1
,那么程序会进入未定义行为,所以我们可以断定 x
的值必然小于或者等于 1,所以现在我们利用 x ≤ 1
这个事实来对相关代码进行优化……”
看似合理,然而它却是不正确的,你能看出来这样的推理错在何处吗?我一时想不起来之前具体的例子了(如果你知道的话告诉我)。上网搜了一下相关话题,发现这篇 Chris Lattner (LLVM 和 Swift 语言 的设计者) 写于 2011 年的文章。文中指出,编译器利用 C 语言的“未定义行为”进行优化,是合理的,对于性能是很重要的,并且举出这样一个例子:
void contains_null_check(int *P) {
int dead = *P;
if (P == 0)
return;
*P = 4;
}
这例子跟我之前看到的 GCC bug 不大一样,但大致是类似的推理方式:这个函数依次经过这样两个优化步骤(RNCE 和 DCE),之后得出“等价”的代码:
void contains_null_check_after_RNCE(int *P) {
int dead = *P;
if (false) // P 在上一行被访问,所以这里 P 不可能是 null
return;
*P = 4;
}
void contains_null_check_after_RNCE_and_DCE(int *P) {
//int dead = *P; // 死代码消除
//if (false) // 死代码
// return; // 死代码
*P = 4;
}
他的推理方式是这样:
- 首先,因为在
int dead = *P
里面,指针P
的地址被访问,如果程序顺利通过了这一行而没有出现未定义行为(比如当掉),那么之后P
就不可能是 null,所以我们可以把P == 0
优化为false
。 - 因为条件是
false
,所以整个 if 语句都是死代码,被删掉。 dead
变量赋值之后,没有被任何其它代码使用,所以对dead
的赋值是死代码,可以消去。
最后函数就只剩下一行代码 *P = 4
。然而经我分析,发现这个优化转换是根本错误的做法(unsound 的变换),而不只是像他说的“存在安全隐患”。现在我来考考你,你知道这为什么是错的吗?值得庆幸的是,现在如果你把这代码输入到 Clang,就算加上 -O3 选项,它也不会给你进行这个优化。这也许说明 Lattner 的这个想法后来已经被 LLVM 团队抛弃。
我写这篇文章的目的其实是想告诉你,不要盲目的相信编译器的作者们做出的变换都是正确的,无论它看起来多么的合理,只要打开优化之后你的程序出现奇葩的行为,你就不能排除编译器进行了错误优化的可能性。Lattner 指出这样的优化完全符合 C 语言的标准,这说明就算你符合国际标准,也有可能其实是错的。有时候,你是得相信自己的直觉……
【转】C 编译器优化过程中的 Bug的更多相关文章
- VS编译器优化诱发一个的Bug
VS编译器优化诱发一个的Bug Bug的背景 我正在把某个C++下的驱动程序移植到C下,前几天发生了一个比较诡异的问题. 驱动程序有一个bug,但是这个bug只能 Win32 Release 版本下的 ...
- SQL优化过程中常见Oracle HINT
在SQL语句优化过程中,我们经常会用到hint,现总结一下在SQL优化过程中常见Oracle HINT的用法: 1. /*+ALL_ROWS*/ 表明对语句块选择基于开销的优化方法,并获得最佳吞吐量, ...
- mysql优化过程中遇见的坑(mysql优化问题特别注意)
不要听信你看到的关于优化的“绝对真理”,包括本文所讨论的内容,而应该是在实际的业务场景下通过测试来验证你关于执行计划以及响应时间的假设. 单条查询最后添加 LIMIT 1,停止全表扫描. 对于char ...
- TensorRT优化过程中的dropout问题
使用tensorRT之前,你一定要注意你的网络结构是否能够得到trt的支持,无论是CNN还是RNN都会有trt的操作. 例如:tf.nn.dropout(features, keep_prob),tr ...
- 【填坑往事】Android手机锁屏人脸解锁优化过程实录
背景 写这篇文章,主要是为了以后面试方便.因为我简历上写了,上一份工作的最大亮点是将人脸解锁的速度由1200ms优化到了600ms,所以这些内容已经回答无数遍了.但每次总觉得回答的不完整,或者说总感觉 ...
- C#编译器优化那点事 c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错 webAPI 控制器(Controller)太多怎么办? .NET MVC项目设置包含Areas中的页面为默认启动页 (五)Net Core使用静态文件 学习ASP.NET Core Razor 编程系列八——并发处理
C#编译器优化那点事 使用C#编写程序,给最终用户的程序,是需要使用release配置的,而release配置和debug配置,有一个关键区别,就是release的编译器优化默认是启用的.优化代码 ...
- Bug,项目过程中的重要数据
作者|孙敏 为什么要做Bug分析? Bug是项目过程中的一个有价值的虫子,它不只是给开发的,而是开给整个项目组的. 通过Bug我们能获得什么? 积累测试方法,增强QA的测试能力,提升产品质量 发现项目 ...
- Coding过程中遇到的一些bug
1. 在使用layoutSubviews方法调整自定义view内部的子控件坐标时,最好不要使用子控件的centerX,centerY属性,否则会出现奇怪的bug. 如果一定要用,务必仔细检查,该子控件 ...
- Visual C++中的编译器优化
博客搬到了fresky.github.io - Dawei XU,请各位看官挪步.最新的一篇是:Visual C++中的编译器优化.
随机推荐
- 低危漏洞- X-Frame-Options Header未配置
原文链接:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/X-Frame-Options?redirectlocale=en-US&redi ...
- volatile synchronized AtomicInteger的区别
在网络上看了很多关于他们两个的区别与联系,今天用自己的话表述一下: synchronized 容易理解,给一个方法或者代码的一个区块加锁,那么需要注意的是,加锁的标志位默认是this对象,当然聪明的你 ...
- 获取请求真实ip
/** * Copyright (c) 2011-2014, James Zhan 詹波 (jfinal@126.com). * * Licensed under the Apache License ...
- Java I/O 操作及优化建议
Java I/O I/O,即 Input/Output(输入/输出) 的简称.就 I/O 而言.概念上有 5 种模型:blocking I/O.nonblocking I/O,I/O multiple ...
- Hive表的建立和导入导出数据
Hive是Hadoop的常用工具之一,Hive查询语言(HiveQL)的语法和SQL类似,基本实现了SQL-92标准. 1. 表的建立 编写以下的文件: USE test; DROP TABLE IF ...
- sell 项目 类目表 设计 及 创建
1.数据库设计 2.类目表 创建 /** * 类目表 */ create table `product_category` ( `category_id` int not null auto_incr ...
- npm 主要命令
本文主要参考自:http://www.runoob.com/nodejs/nodejs-npm.html 1.使用 npm 命令安装模块 $ npm install express var expre ...
- 20160210.CCPP体系具体解释(0020天)
程序片段(01):01.二级指针.c 内容概要:二级指针 #include <stdio.h> #include <stdlib.h> //01.二级指针: // 1.使用场景 ...
- LeetCode 225 Implement Stack using Queues(用队列来实现栈)(*)
翻译 用队列来实现栈的例如以下操作. push(x) -- 将元素x加入进栈 pop() -- 从栈顶移除元素 top() -- 返回栈顶元素 empty() -- 返回栈是否为空 注意: 你必须使用 ...
- python 测试时一个str是不是字符串
# -*- coding: cp936 -*- #python 27 #xiaodeng #测试时一个str是不是字符串 def isAstring(obj): ''' 测试一个str是不是字符串 b ...