目录
一、引入
二、volatile关键字
三、对编译器优化的理解
一、引入
我们先来看一段代码:
#include <stdio.h>
#include <signal.h>int flag = 1;void handler(int signo)
{flag = 0;printf("已收到%d号信号,flag:%d\n", signo, flag);
}int main()
{signal(2, handler);while (flag);printf("进程退出\n");return 0;
}
我们编译一下运行:
可以看到当我们按下Ctrl+c传给进程2号信号时,进程执行了handler方法将全局变量flag改成了0,while循环条件不为真,循环结束进程退出
没问题,结果也符合我们的预期
下面我们在gcc后面加上-O选项来看看:
-O 是 gcc 编译器的一个优化选项,用于告诉编译器进行优化以提高程序的执行效率。
选项 -O 后面可以跟一个数字,表示优化级别。常见的优化级别包括 -O0、-O1、-O2 和 -O3,数字越大表示优化级别越高。
咦?优化情况下,键入Ctrl+c,2号信号被捕捉,执行自定义动作,修改 flag=0 ,但是 while 条件依旧满足,进程继续运行!但是很明显flag肯定已经被修改了,但是为何循环依旧执行?这和我们逻辑上预期的结果并不一样啊?
下面我们来分析一下出现该情况的原因:
我们都知道while想要获取flag的值就必须将内存中存储的数据拿到CPU的寄存器当中:
但是在进程收到2号信号时,handler方法肯定将内存中的flag数据修改为0了,所以很明显,while 循环检查的flag, 并不是内存中最新的flag
这是因为编译器发现while循环体内并没有将flag的数据做任何修改,但是每一次循环将flag的数据拿到CPU中是IO操作,IO操作效率低下,因此在1/2/3的优化级别下,编译器将汇编代码的IO操作只进行一次,这样子后续的while循环直接从寄存器中拿数据即可
这就存在了数据二异性的问题。 while 检测的flag其实已经因为优化,被放在了 CPU寄存器当中。如何解决呢?
二、volatile关键字
C语言中的volatile关键字就可以解决上述问题,我们将全局变量flag前加上该关键字试试看:
#include<stdio.h>
#include<signal.h>volatile int flag=1;void handler(int signo)
{flag=0;printf("已收到%d号信号,flag:%d\n",signo,flag);
}int main()
{signal(2,handler);while(flag);printf("进程退出\n");return 0;
}
在不同优化级别下的运行结果:
可以看到加上该关键字后,进程都成功退出了
所以该关键字的作用显然易见:
volatile 作用:保持内存的可见性,告知编译器,被该关键字修饰的变量,不允许被优化,对该变量的任何操作,都必须在真实的内存中进行操作
三、对编译器优化的理解
我们从上述的例子中可以看出:编译器的优化本质上是对代码动手脚!
优化编译后的代码,确实可以提高程序的性能和执行效率。但是需要注意的是,在某些情况下,过高的优化级别可能会导致编译时间延长、代码变得难以调试或者得到错误的结果。因此,在选择优化级别时需要根据具体情况进行权衡。