c 关键字

volatile(不稳定)

C语言中volatile关键字很多人都掌握好,很多C书籍也是一两行概括。我将会教你一个合适的方式,理解volatile

首先,在嵌入式C或C++代码中,你是否遇到过下面几个情形?

  • 只要不开启编译器优化,代码工作得很好。
  • 只要中断没有使能,代码工作得很好-
  • 片状硬件驱动
  • 没有其他进程,RTOS(实时操作系统)任务单独运行将会很好。

只要你认为其中一个是这样的,说明你还不了解volatile关键字。C中volatile在变量声明的时候一个修饰符。它将会告诉编译器这个变量将会随时改变,编译器无论怎么样都不会在该代码附近找到这个变量值。这个声明是相当严格的。在验证他们之前,我们先看下语法。

c volatile关键字语法

在变量数据前面或后面加volatile关键字,来声明一个volatile变量。例如,一个整型变量foo,的两种方式。

volatile int foo;
int volatile foo;

现在,volatile通常见于指针的应用中,尤其是在内存映射I/O寄存器中。下面都是申明一个pReg指向一个volatile无符号8位整型。

volatile uint8_t * pReg;
uint8_t volatile * pReg;

volatile指向一个不是常变的数据很少见(我认为我用过),但是我还是将继续给出使用的语法:

int *volatile p;

下面只是为了完整性,如果你非要声明一个volatile指针指向一个volatile变量,应该写成下面形式:

int volatile*volatile p;

最后,如果你将volatile修饰一个结构体或者两合体,那么他们所有的内容都是volatile。也可以在内部成员进行单独声明。

合理使用volatile关键字

一个变量无论在何时都可能有不可预期的变化,应该将其声明为volatile型变量。实际上,只有三种类型变量能够这样的改变:

  • 内存映射下外围设备寄存器
  • 中断服务例程修改的全局变量
  • 含有多线程的所任务能够访问的全局变量。

我们将会讨论每种情况。

外围设备寄存器

嵌入式系统包含实时硬件,通常有复杂的外围设备。这些外围设备包含改变值的寄存器,寄存器的值和程序流程是异步的。举个简单例子,一个8位状态寄存器,并且映射内存地址是0x1234。在非零之前要获取寄存器的状态。天真,错误的方法如下:

//错误的方法
uint8_t *pReg=(uint8_t*)0x1234;
//wait for register to become non-zero
while(*pReg==0){
    //do something else
}

只要你打开编译器的优化,这个几乎可认为是错误的,由于编译器将会产生先下面的汇编代码:

mov ptr,#0x1234
mov a,@ptr
loop:
    bz loop

编译器优化的原理很简单:已经读到变量值到累加器(第二行),不再重复读它,因为这个值通常是一样的。因此,第三行将会无限的进入循环中。为了让编译器做我们想做的方式,应该修改成这样:

//正确的申明
uint8_t volatile*pReg=(uint8_t volatile*)0x1234;
while(*pReg==0){
    //do something else
}

这个将会产生这样的汇编:

mov ptr,#0x1234
loop:
    mov a,@ptr
    bz loop

这就是我们完成我们想要的方式。

具有特殊属性寄存器将会引起更微妙的问题。例如,很多外围设备只是简单读取包含的寄存器,比你想要的多或少的读将会导致不可预期的结果。

中断服务例程

中断服务例程通常设置尝试变量再主线代码中。例如,一些列端口中断将会测试接收到的字符看是否是一个ETX字符(认为标注消息结束)。如果这个字符是ETX,ISR(中断服务程序)将会设备一个全局标志位。一个不正确的例子,可能是这样:

//错误的程序
int ext_rcvd=FALSE;
void main(){
    ...
    while(!ext_rcvd){
        //wait
    }
    ...
}
interrupt void rx_isr(void){
    ...
    if(EXT==rx_char){
        etx_rcvd=TRUE;
    }
    ...
}

当编译器优化关闭,这个代码可能正常。然而只要50%优化将会导致代码错误。问题在于编译器不知道ext_rcvd将会在ISR改变。只要将变量声明为volatile将会解决这个问题。

bool volatile ext_rcvd=FALSE;
void main(){
    ...
    while(!ext_rcvd){
        //wait
    }
    ...
}
interrupt void rx_isr(void){
    ...
    if(EXT==rx_char){
        etx_rcvd=TRUE;
    }
    ...
}
应用多线程

尽管有队列,管道和其他调度机制在实时系统中,相对常见的两个进程通过共享内存(也就是一个全局的)去两个进程通讯。即使你添加抢占式调度方法,你的编译器还是不知道切换到什么样的上下文或什么时候切换。因此,另一个进程修改一个共享全局,和中断服务程序相类似。所以,所有全局变量应该声明为valatile变量。.应当这样声明:

int volatile cntr;
void stask(void){
    cntr=0;
    while(cntr==0){
        sleep(1);
    }
    ...
}
void task2(void){
    ...
    cntr++;
    sleep(10);
    ...
}

最后思考

一些编译器允许你将所有变量隐式申明为volatile。.抵住这样的诱惑,因为它本质是一种思想的替换。也会导致一些可能效率问题。

当然,抵住这个诱惑去怪编译器或关闭。现在的编译器是很好了,我都忘了上次遇到编译器优化问题是在什么时候了。相反的,我遇到编程者过度使用volatile失败的案例。

如果你给一些代码片区修改,用grep关键字volatile,居然没有,这个例子是很好去找问题的地方。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值