记一个奇怪的gcc编译优化:-ftree-vrp
创始人
2024-05-11 20:49:08
0

记一个奇怪的gcc编译优化:-ftree-vrp

最近有同事遇到一个gcc不同编译优化选项结果不一致的问题,从该问题反映出编程规范(我更倾向于华为内部使用的”编程军规“的叫法)的问题非常有参考意义,在此分享给大家。

程序可简化如下:

#include int main() {int num = 0;int arr[5] = {10, 10, 10, 10, 10};while ((arr[4 - num] == 10) && (4 - num >= 0)) {num++;printf("while loop!\n");}printf("num = %d\n", num);int index = 4 - num;printf("index = %d\n", index);bool flag = index >= 0 ? 1 : 0;printf("flag = %d\n", flag);if (index >= 0) {printf("error!\n");}return 0;
}

当然这段代码有不少不符合编程军规的地方,这里先按下不表,下文再分析,这里先只看其功能。从代码逻辑来看,应该可以得到下面的打印结果:

while loop!
while loop!
while loop!
while loop!
while loop!
num = 5
index = -1
flag = 0

O0和O1编译( ”g++ hello.cpp -O1“ )后运行结果也确实如此,这符合我们的期望。但是选择O2编译( ”g++ hello.cpp -O2“ )后,却得到如下结果:

while loop!
while loop!
while loop!
while loop!
while loop!
num = 5
index = -1
flag = 1
error!

计算的 num index 一样,但基于 index 的范围判断却得到完全不同的结果,看上去似乎是O2编译优化出错了。想找到哪个优化pass导致这个问题倒也不难,我们可以尝试将gcc的O2比O1多的优化pass拿出来分别进行编译测试。这里列举了gcc各个优化等级所采用的优化pass。通过阅读O2的pass和实际测试,找到了肇事元凶。

-ftree-vrp

Perform Value Range Propagation on trees. This is similar to the constant propagation pass, but instead of values, ranges of values are >propagated. This allows the optimizers to remove unnecessary range checks like array bound checks and null pointer checks. This is >enabled by default at -O2 and higher. Null pointer check elimination is only done if -fdelete-null-pointer-checks is enabled.

使用 ”g++ hello.cpp -O1 -ftree-vrp“ 编译即可得到上面的错误结果,也即在O1的基础上加 " -ftree-vrp“ 优化pass。 " -ftree-vrp“ 的功能有点类似常量传播,但它传播的不是值,而是值的范围,也就是说编译器可以删除不必要的值范围检查,例如数组下标。

具体到我们这个问题,编译器认为 4 - num 是数组下标,是一个非负数,因而在13和15行地方不会真实的去判断 index 值的的范围,而直接认为它是非负的,从而得到错误的结果。这一点也可以直接从两种编译选项下( ”g++ hello.cpp -O1“ ”g++ hello.cpp -O1 -ftree-vrp“ )的反汇编得到印证:

在这里插入图片描述
在加了 " -ftree-vrp“ 优化(右图)后,源码13行这里直接将数值 1 给了 flag,源码15行这里也省去了判断,直接调用打印函数。

此外,需要注意的是 " -ftree-vrp“ 的优化是传播的是值范围而不是值,因此源码11行 index 的计算没有问题。

讲道理,编译器优化要优先保证程序的正确性,其次才是提升性能。这个优化是否有点争议这里不好说,也没查到类似的资料。我们回到上面的代码,现在可以说一说这段代码的问题了。

  • 第一,在循环体内修改了循环变量,这个是编程军规明令禁止的。
  • 第二,访问数组时,没有先检查数组下标是否越界, while 循环的最后一次循环时,也即 index=5 的时候发生数组读越界,这里应该将两个条件调换一下顺序:
#include int main() {int num = 0;int arr[5] = {10, 10, 10, 10, 10};// while ((arr[4 - num] == 10) && (4 - num >= 0)) {while ((4 - num >= 0) && (arr[4 - num] == 10)) {num++;printf("while loop!\n");}printf("num = %d\n", num);int index = 4 - num;printf("index = %d\n", index);bool flag = index >= 0 ? 1 : 0;printf("flag = %d\n", flag);if (index >= 0) {printf("error!\n");}return 0;
}

根据短路原则,第一个条件不满足时,就不会再进行第二个条件判断了,也就不会发生数组读越界。并且这样修改后,使用 ”g++ hello.cpp -O1 -ftree-vrp“ 编译结果也是正确的:

while loop!
while loop!
while loop!
while loop!
while loop!
num = 5
index = -1
flag = 0

我的理解是这样的:因为第一个条件 (4 - num >= 0) 不满足,编译器就能发现 4 - num 已经小于 0 了。

从这个问题可以看出,编程军规的重要性,上面说的两个问题都是编程军规应该明令禁止的。对于一个团队,可能大家水平层次不齐,制定规范的编程军规供大家学习并要求执行,可以很好的杜绝这些问题。

最后,附上一个不错的cpp编程规范。

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
【PdgCntEditor】解... 一、问题背景 大部分的图书对应的PDF,目录中的页码并非PDF中直接索引的页码...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
修复 爱普生 EPSON L4... L4151 L4153 L4156 L4158 L4163 L4165 L4166 L4168 L4...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...