分类: C/C++

Strict Aliasing,神坑?

先来看一段代码:

你觉得程序的输出是什么样的呢?

代码很容易理解,只做了一件事情,把 input变量储存的32位整数的高16位和低16位交换,存放在 output变量中,并输出这两个变量。

相信很多人都写过这样的代码(至少我写过),虽然觉得有点怪,但应该不会有什么问题,于是编译运行:

结果完全符合预期,似乎没有任何问题啊!

别高兴的太早,让我们试试打开编译器优化选项 -O2重新编译运行:

问题出现了, output变量居然不符合预期?什么鬼?!

PS:似乎有必要注明用来测试的g++版本,我测试用的版本是4.4.7,经测试高版本g++得到的结果会不同,但这个问题仍然是存在的。

strict-aliasing


难道是编译器优化有问题么?但怀着对GNU的景仰…不,一定是我的使用方式有问题!

编译出现诡异问题怎么办?不要忘了使用 -Wall,编译器会给你线索:

编译器果然给出了警告:我们的指针操作破坏了strict-aliasing规则,新的问题来了,什么是strict-aliasing?严格别名(非准确翻译)?

man g++中找到这样一段介绍:

简单来说,如果在编译器中开启了 -fstrict-aliasing选项( -O2优化级别默认开启这个选项),编译器会在“不同类型的变量一定存放在不同的内存空间中”的假定条件下对代码进行优化。

这实际是一个普通程序员和编译优化器编写者之间的约定:为了方便编译优化器的编写者写出更好的编译器优化功能,普通程序员在编写代码时要遵循这样的约定:“不同类型的变量一定存放在不同的内存空间中”。

反过来看看我们代码中的指针 pipo,他们是 short *类型,但他们指向的内存空间实际上是 int类型的 input变量,这就违反了strict-aliasing规则,但在开启 -O2优化时我们却告诉编译优化器我们遵守了strict-aliasing规则(默认开启),导致编译器做出了“错误”的优化。

来点汇编


明白了问题出现的原因,不妨看看编译器最终生成的汇编代码是怎样的:

看不懂是正常的… -O2级别的优化已经把代码搞的乱七八糟了。 main函数中没有调用 exchange函数的部分,进行了inline优化。

重要的是在调用第二个 printf函数的传参部分,可以看到 0x4(%esp)被直接赋予了 $0xababbaba并且没有修改过。

让我们尝试从编译器的角度思考我们的代码:函数 exchange中修改的内存都是 short类型的,既然程序员承诺遵循strict-aliasing规则,那么函数 exchange就不会修改 int类型的变量 output,所以可以优化一下直接输出初始值就可以了。

好聪明的编译器!好悲剧的程序猿…

怎么办?


那么这样的问题该如何避免呢?显然的,如果你告诉编译器遵循strict-aliasing规则,那在写代码的过程中就不应该尝试去打破这样的规则。但是我们在写C/C++代码的过程中常常需要编写这样一些打破规则的trick代码,与其让自己不自在,不如在编译时不要和编译器做这样的约定(使用 -fno-strict-aliasing编译参数),虽然不能让编译器做一些更加高效的优化,但安全总是第一位的,不是么?

参考资料