gcc 太聪明了!

gcc 越来越聪明了,有时候为了故意不让它优化某一部分代码(但同时还要开启-O!)我真可是费尽了心思。

举两个简单的例子来说明一下。一个是exp()的问题,不带-lm编译下面这段代码:

[c]

include

include

int main(void)
{
printf(“%lf”, exp(4.0));
return 0;
}
[/c]

你会发现可以得出正确的结果,没任何链接错误。它被预处理掉了吗?gcc -E告诉我们没有。那到底怎么回事呢?看一下生成的汇编,你会发现里面根本就没有call exp这样的指令!也就是说它被gcc在编译的时候处理了!这是因为像exp(4.0)这样的常量表达式是可以在编译的时候优化的,它的结果是个常量,所以gcc编译的时候就已经把这个值计算出来了,并把计算结果作为一个常量放到那个位置去了!为我们省去了一个函数调用!:-) 所以,如果你把上面的4.0换成一个double变量传递给exp(),你就会得到链接错误了。

另外一个例子是一个非常真实的例子。我们知道内核中有个著名的结构体叫struct skbuff,而我们公司内部的一个补丁中在某个函数中错误地把skbuff拼写成了skbuf!但编译和运行没任何问题!这个问题是后来别人在code review的时候才发现的!为什么呢?这明明应该通不过编译啊!我花费了不少时间去追踪这个问题,最后发现了问题的根源。为了方便叙述,我把问题的关键部分抽象成了下面的代码:

[c]
int foo(struct non_exist *n)
{
if (n)
return 0;
else
return 1;
}
[/c]

我们这么编译:gcc -c foo.c,你会发现我们只有警告,没有错误!警告我们可以忽略,因为我们只关心为什么不会有错误,换句话说,为什么gcc依旧可以成功地编译出代码来?仔细想一下你会明白,其实在foo()里面,我们根本就不用关心n到底是一个什么样的指针,只要知道它是一个指针就可以了!因为我们一没有对它进行dereference操作,二没有进行++或者—运算!而所有指针在特定的机器上长度是一定的。这正是为什么它可以通得过编译,但警告是肯定不会少的。;-)

如果你读过-O(或以上)生成的汇编的话,你还会发现一大坨gcc优化的例子,这可苦了我这种读汇编的人了,每次都得去猜gcc到底做了那些优化,不过这也相当有乐趣!