volatile修饰符在C语言中的用法
本文章将讨论volatile这个修饰符在C语言中的使用。了解这个修饰符的程序员都知道,最为一个指令关键字,其作用是确保指令本身不会受到编译器的优化而产生未知的程序bug。对此,本文将结合嵌入式中C语言的实际运用来详细讲解。
volatile关键字告诉编译器,这个变量可能在任何时刻都会被改变。有时变量的改变对编译器来说有点“隐蔽”,也许就是这种隐蔽让编译给我们的程序造成了一些未知的bug。
语法:
首先我们来看看volatile的使用方法,通过下面的两种方法实现对int变量的修饰;
ü int volatile var;
ü volatile int var;
当然如果要修饰一个指针变量可以如下
ü int volatile * pointer;
ü volatile int * pointer;
就C语言中的使用volatile来说,一般我们会在一下三种情况中使用volatile关键字
? 映射的外设SFR(Special Function Register)
? 在中断服务函数中修改全局变量
? 在多线程中修改的全局变量
特殊功能寄存器:
在嵌入式偏硬件方面的程序,我们经常要控制一些外围硬件设备,就拿最简单的I/0端口来说,我们会去操作映射到对应IO端口的寄存器。
假设某一个寄存器的地址为0x1234,在C语言中,我们可以定义一个指针指pReg向这个地址:
unsigned int * pReg = (unsigned int *)0x1234;
在实际运用中(例如uart、ADC等等),我们经常回去判断一个寄存器中的值(或者寄存器中某一位)为‘0’还是‘1’。例如下程序:
unsigned int *pReg = (unsigned int *)0x1234;
while(*pReg);//wait
//Code...
我们的代码目的是不断的判断*pReg的值是否为‘0’。
如果在编译程序时,加上了优化等级。那么编译如果认为while(0 == *pReg)是恒成立的,则就会编译成以下代码(反汇编)
30: b 30
上面的程序时死循环,所以就算*pReg的内容变了,程序也不会有任何变化,这样就导致产生了严重的bug。如果改成volatile unsigned int *pReg = (unsigned int *)0x1234;
则反汇编代码如下:
28: ldr r3,
[r2, #564]
2c: cmp r3,
#0
30: bne 28
所以,加上volatile关键字修饰了 pReg变量后,编译器没有优化其代码,还是老老实实的去pReg所指向的地址里取值,然后和零进行比较。这样就不会产生上面的bug了。
中断中使用全局变量:
在中断程序中使用全局变量时,我们程序的逻辑也许没有任何问题,但是并不代表编译器就能完完全全能按照我们所期待的去翻译我们的代码。
首先我们来看看下面的代码,其中do_interrupt 是ISR(Interrupt service routines)
static int flag = 1;
void main( void )
{
while (flag)
{
//code ...①
}
//code ...②
}
void do_interrupt( void
)
{
//code...
flag = 0;
}
上面的代码很简单,只要flag的值为‘1’,那么就会一直= 1 \* GB3①的代码,但是如果产生了中断,则在中断服务函数do_interrupt中会改变flag 的值 为‘0’。但是编译器却不知道这个事情,所以当我们打开了优化后,编译翻译成了下面的代码:
反汇编:
10: eafffffe b 10
又是一个 死循环….,根本不去判断flag的值,所以就算中断里面改变了flag的值,也不会有任何影响。这样代码运行逻辑就不是我们所设计的逻辑了。
当变量flag 加上了volatile关键字修饰后,
volatilestatic int flag = 1;
反汇编如下:
4: e5923000
ldr r3,
[r2]
8: e3530000
cmp r3,
#0
c: 1afffffc
bne 4
所以程序还是会不断的判断flag的值,当有中断产生并且修改flag的值时,代码能够按照我们的逻辑运行。
中断中使用全局变量:
在多线程变成中,我们先不考虑管道、队列、信号灯集等等情况。还是看一个非常简单的情况,全局变量…首先看看以下代码
int cnt;
void task1(void)
{
cnt = 0;
while (cnt == 0) {
sleep(1);
}
}
void task2(void)
{
cnt++;
sleep(10);
}
上面的代码,其中task1 和 task2 分别为两个线程完成的任务。其中cnt为全局变量。所以,理解这个多线程的例子和中断的例子类似。为了能够真正的保持task1 和 task2两个线程同步操作,我们可以利用volatile关键字来修饰 cnt 这个全局变量。