volatile实例
一、volatile的汇编解释
main.c:
优化要开得比较高,gcc -O2 -S main.c得到
cmpl $100, a //先比较a和100
je .L4 //相等就跳到L4
..........
ret
.L4:
jmp .L4 //L4再跳到L4,就是把和a比较的部分给删了,来了死循环,因为编译器认定了a不会变
..........
.data
.align 4
.type a, @object
.size a, 4
a:
.long 100
加上了volatile int a = 100就变成了:
.L3:
movl a, %eax
cmpl $100, %eax
je .L3
也是个一直从L3调回L3的过程,但是L3本身包括了对a是否等于100的验证。
总之,volatile至少一点效果是告诉编译器,这个变量别去乱优化,不像你想象的那样。
二、volatile实用举例
对于一中的int a,怎么都想不到要变的方法。改一下就可以了:
gcc -O2 main.c之后执行,kill -SIGUSR1 进程号是没有用的,就像之前说的已经优化成死循环了,改成volatile int a就可以了。
另一个简单的共享内存例子:
汇编代码为:
........
movb $1, (%eax)
.L2:
jmp .L2 //有是自己跳回自己,根本就没有去看*pc的值
........
改下代码得到个反例:
像预想的那样,非volatile的-O2完全就优化成了死循环,而volatile的则在子进程打印后父进程打印(当然具体退出时间前后不一定)。
总之,编译器往往“聪明反被聪明误”,像上面的a和*pc都是对程序员可预期会变的,但是编译器看不出来,这时就要用volatile明确地告知。
一、volatile的汇编解释
main.c:
#include <stdio.h>
int a = 100;
void main(){
while(a == 100){}
printf("finally...\n");
}
优化要开得比较高,gcc -O2 -S main.c得到
cmpl $100, a //先比较a和100
je .L4 //相等就跳到L4
..........
ret
.L4:
jmp .L4 //L4再跳到L4,就是把和a比较的部分给删了,来了死循环,因为编译器认定了a不会变
..........
.data
.align 4
.type a, @object
.size a, 4
a:
.long 100
加上了volatile int a = 100就变成了:
.L3:
movl a, %eax
cmpl $100, %eax
je .L3
也是个一直从L3调回L3的过程,但是L3本身包括了对a是否等于100的验证。
总之,volatile至少一点效果是告诉编译器,这个变量别去乱优化,不像你想象的那样。
二、volatile实用举例
对于一中的int a,怎么都想不到要变的方法。改一下就可以了:
#include <stdio.h>
#include <signal.h>
int a = 100;
void handler(int num){
a = 200;
}
void main(){
signal(SIGUSR1,handler);
while(a == 100){}
printf("finally...\n");
}
gcc -O2 main.c之后执行,kill -SIGUSR1 进程号是没有用的,就像之前说的已经优化成死循环了,改成volatile int a就可以了。
另一个简单的共享内存例子:
//仅作为演示用,异常情况未作处理
#include <sys/shm.h>
#include <stdio.h>
int main(){
int shmid = shmget(1234,sizeof(char),0666|IPC_CREAT);
void *shm = shmat(shmid,NULL,0);
char *pc = shm;
*pc = 1;
while(*pc){}
printf("finally");
shmdt(shm);
}
汇编代码为:
........
movb $1, (%eax)
.L2:
jmp .L2 //有是自己跳回自己,根本就没有去看*pc的值
........
改下代码得到个反例:
//仅作为演示用,异常情况未作处理
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
int shmid = shmget(1234,sizeof(char),0666|IPC_CREAT); //共享内存相关函数不多,用法也方便,不熟悉百度也能两下上手
if(fork() == 0){ //子进程
sleep(10); //休息十秒好看打印结果
void *shm = shmat(shmid,NULL,0); //子进程能从副进程出继承到变量的值,并且往往是写时才复制
char *pc = shm;
*pc = 0;
printf("son:done\n"); //改完了,打印done
shmdt(shm);
exit(0);
}
void *shm = shmat(shmid,NULL,0); //不在这之后fork是shm返回的线性地址空间子进程和父进程完全是两码事
char *pc = shm;
//volatile char *pc = shm;
*pc = 1;
while(*pc){}
printf("finally");
shmdt(shm);
}
像预想的那样,非volatile的-O2完全就优化成了死循环,而volatile的则在子进程打印后父进程打印(当然具体退出时间前后不一定)。
总之,编译器往往“聪明反被聪明误”,像上面的a和*pc都是对程序员可预期会变的,但是编译器看不出来,这时就要用volatile明确地告知。