编译器是怎样优化代码的?如何查看编译器是否优化代码?怎样避免编译器优化代码?volatile关键字的使用

上次我们说到现代编译器已经非常聪明,为了保证程序的执行效率,会在编译时对代码做优化。水平较低的程序员写出的代码比较臃肿,编译器的优化确能够增加程序的执行效率。但是,编译器有时“聪明过了头”,自以为是的把有用的语句优化掉了,反而导致程序不能正常工作。

例如下面这几句代码,

int a;
a = 0x10;       // 10000
a = 0x08;       // 01000

编译器很聪明,会认为 “a = 0x10” 这句没有意义,因为我们并没有使用 a 的 0x10 这个值,在下一句就被 0x08 覆盖了。所以在编译时,编译器直接就忽略了 “a = 0x10” 这句,这样的确会增加效率,但是在某些情况下,即使只是赋值,也是有意义的。

还记得我们在《程序员编写的代码为什么可以控制计算机硬件工作?》一节中提到的电灯开关的例子吗?我们用 0 表示灭灯,用 1 表示开灯。

上图的电灯状态显然可以用 01000 表示。

现在假设我们用变量 a 表示电灯的状态,那么“a = 0x10” 这句就表示开最左边的灯,它是有意义的。

本来电灯管理员想先开一下最左边的灯,看看它亮不亮,有没有坏掉,那现在编译器把这句忽略了,最左边的灯即使没坏也不亮,会害的电灯管理员花很多时间检查电路。

这就是编译器“自作聪明”带来的问题。那我只能不让编译器做优化了吗?答案是没有必要因为这点小问题,就全盘否定编译器优化带来的效率提升。C99 提供了 "volatile" 关键字,就是专门用来解决这样的问题的。

"volatile"的字面意思是“不稳定的,易变的”,它可以用来修饰变量,以此来告诉编译器,别优化我。编译器看到以后,就知道下面这几行代码不需要优化,不管自己看着多么不爽,也照样执行,不做任何优化:

volatile int a;
a = 0x10;       // 10000
a = 0x08;       // 01000

使用了 volatile 关键字修饰代表电灯开关的变量 a,编译后,会发现最左边的电灯也亮了一下。电灯管理员知道它没坏,可以安心了。

你可能会说,我编程不涉及硬件,这个关键字用不到。可是,即使如此,编译器的优化,有时候也会让人觉得很困惑。

请看下面这段代码:main 函数一直在判断变量 is_continue 的值是否变为 0,如果变了,就在终端打印出 “hello world”,否则就一直等下去。thread 函数在一秒后,将is_continue 的值修改为 0。

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

int is_continue = 1;

void* thread(void* p)
{
    sleep(1);
    is_continue = 0;
    return NULL;
}

int main()
{
    pthread_t ppid;
    pthread_create(&ppid, NULL, thread, NULL);

    while(is_continue);

    printf("hello world\n");
    return 0;
}

可以看出,原本程序员的意图是通过变量 is_continue 控制 main 打印出 "hello world" 的时机(这里是1秒后打印)。我们编译它,执行会发现,程序一直不打印“hello world”,这很奇怪。我们一起来看看编译后,程序的汇编代码:

$ gcc t.c -lpthread -O2 -g
$ objdump -dS a.out 

a.out:     file format elf64-x86-64
...

这就明白了,编译器“自作聪明”的优化了代码,导致程序没有按照程序员的意图运行。

编译器认为:在 main 函数中,没有任何对 is_continue 的修改,while() 也是什么都没做,所以只需要判断 is_continue 最开始的值即可,不需要再从内存重新读取 is_continue

接下来,我们在“int is_continue = 1;”前加上 “volatile”关键字,其他的代码都不变,再编译执行,发现 1 秒后,终端有“hello world”打印。查看相应的汇编代码:

编译器见到“volatile”关键字后,不再自作主张,每次都从内存读取 is_continue 再做判断,程序终于与预期一致了。

阅读更多:   杂谈 , C语言 , Linux笔记
添加新评论

icon_redface.gificon_idea.gificon_cool.gif2016kuk.gificon_mrgreen.gif2016shuai.gif2016tp.gif2016db.gif2016ch.gificon_razz.gif2016zj.gificon_sad.gificon_cry.gif2016zhh.gificon_question.gif2016jk.gif2016bs.gificon_lol.gif2016qiao.gificon_surprised.gif2016fendou.gif2016ll.gif