volatile关键字的用法

本文深入讲解了C语言中volatile关键字的使用场景与原理,通过多个实例演示了如何防止编译器优化导致的数据读取错误,并探讨了在中断服务程序、多任务环境及硬件寄存器映射中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 


 

volatile 的本意是 易变的 ”. 由于访问寄存器的速度要快过 RAM, 所以编译器一般都会作减少存取外部 RAM 的优化 . 比如 :
static int i = 0 ;
int main(void)
{
...
while (1)
{
if (i)
do_something() ;
}
}
 
/* Interrupt service routine. */
void ISR_2(void)
{
i = 1 ;
}
 
程序的本意是希望 ISR_2 中断产生时 , main 当中调用 do_something 函数 , 但是由于编译器判断在 main 函数里面没有修改过 i, 因此可能只执行一次对从 i 到某寄存器的读操作 , 然后每次 if 判断都只使用这个寄存器里面的 “i 副本 ”, 导致 do_something 永远也不会被调用 . 如果将将变量加上 volatile 修饰 , 则编译器保证对此变量的读写操作都不会被优化 ( 肯定执行 ). 此例中 i 也应该如此说明 .
一般说来 ,volatile 用在如下的几个地方 :
1. 中断服务程序中修改的供其它程序检测的变量需要加 volatile;
2. 多任务环境下各任务间共享的标志应该加 volatile;
3. 存储器映射的硬件寄存器通常也要加 volatile 说明 , 因为每次对它的读写都可能有不同意义 ;
另外 , 以上这几种情况经常还要同时考虑数据的完整性 ( 相互关联的几个标志读了一半被打断了重写 ), 1 中可以通过关中断来实现 ,2 中可以禁止任务调度 ,3 中则只能依靠硬件的良好设计了 .
 
现举例说明 ( Keil-c a51 为例 , 例子来自 Keil FQA), 看完例子后你应该明白 volatile 的意思了 .
 
1.
void main (void)
{
volatile int i ;
int j ;
i = 1 ;  //1   不被优化 i=1
i = 2 ;  //2   不被优化 i=2
i = 3 ;  //3   不被优化 i=3
 
j = 1;  //4   被优化
j = 2;  //5   被优化
j = 3;  //6  j = 3
}
2.
函数 :
void func (void)
{
unsigned char xdata xdata_junk ;
unsigned char xdata *p = &xdata_junk ;
unsigned char t1, t2 ;
 
t1 = *p ;
t2 = *p ;
}
编译的汇编为 :
0000 7E00    R     MOV     R6,#HIGH xdata_junk
0002 7F00    R     MOV     R7,#LOW xdata_junk
;---- Variable 'p' assigned to Register 'R6/R7' ----
0004 8F82          MOV     DPL,R7
0006 8E83          MOV     DPH,R6
;!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 注意
0008 E0            MOVX    A,@DPTR
0009 F 500    R     MOV     t1,A
000B F500    R     MOV     t2,A
;!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
000D 22            RET     
 
将函数变为 :
void func (void)
{
volatile unsigned char xdata xdata_junk ;
volatile unsigned char xdata *p = &xdata_junk ;
unsigned char t1, t2 ;
t1 = *p ;
t2 = *p ;
}
编译的汇编为 :
0000 7E00    R     MOV     R6,#HIGH xdata_junk
0002 7F00    R     MOV     R7,#LOW xdata_junk
;---- Variable 'p' assigned to Register 'R6/R7' ----
0004 8F82          MOV     DPL,R7
0006 8E83          MOV     DPH,R6
;!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
0008 E0            MOVX    A,@DPTR
0009 F 500    R     MOV     t1,A         a
000B E0            MOVX    A,@DPTR
000C F500    R     MOV     t2,A
;!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
000E 22            RET     
比较结果可以看出来 , 未用 volatile 关键字时 , 只从 *p 所指的地址读一次 ,
如在 a *p 的内容有变化 , t2 得到的则不是真正 *p 的内容 .
3.
volatile unsigned char bdata var ;  // use volatile keyword here
sbit var_0 = var^0 ;
sbit var_1 = var^1 ;
unsigned char xdata values[10];
 
void main (void) 
{
unsigned char i ;
for (i = 0; i < sizeof (values); i++)
{
var = values[i] ;
if (var_0)
{
var_1 = 1 ; // a
values[i] = var ;  // without the volatile keyword, the compiler
// assumes that 'var' is unmodified and does not
// reload the variable content.
}
}
}
在此例中 , 如在 a 处到下一句运行前 ,var 如有变化则不会 , var=0xff; 则在 values[i] = var; 得到的还是 values[i] = 1;
应用举例 :
1.
#define DBYTE ((unsigned char volatile data  *) 0)
说明 : 此处不用 volatile 关键字 , 可能得不到真正的内容 .
2.
#define TEST_VOLATILE_C
//***************************************************************
// verwendete Include Dateien
//***************************************************************
#if __C51__ < 600
#error: !! Keil 版本不正确
#endif
//***************************************************************
// 函数 void v_IntOccured(void)
//***************************************************************
extern void v_IntOccured(void);
//***************************************************************
// 变量定义
//***************************************************************
char xdata cvalue1;          // 全局 xdata
char volatile xdata cvalue2; // 全局 xdata
//***************************************************************
// 函数 : v_ExtInt0()
// 版本 :
// 参数 :
// 用途 : cvalue1++,cvalue2++
//***************************************************************
void v_ExtInt0(void) interrupt 0
{
cvalue1++;
cvalue2++;
}
//***************************************************************
// 函数 : main()
// 版本 :
// 参数 :
// 用途 : 测试 volatile
//***************************************************************
void main()
{
char cErg;
//1. 使 cErg=cvalue1;
cErg = cvalue1;
//2. 在此处仿真时手动产生中断 INT0, 使 cvalue1++; cvalue2++
if (cvalue1 != cErg)
v_IntOccured();
//3. 使 cErg=cvalue2;
cErg = cvalue2;
//4. 在此处仿真时手动产生中断 INT0, 使 cvalue1++; cvalue2++
if (cvalue2 != cErg)
v_IntOccured();
//5. 完成
while (1);
}
//***************************************************************
// 函数 : v_IntOccured()
// 版本 :
// 参数 :
// 用途 : 死循环
//***************************************************************
void v_IntOccured()
{
 while(1);
}
仿真可以看出 , 在没有用 volatile , 2 , 程序不能进入 v_IntOccured(); 但在 4 处可以进入 v_IntOccured().
volatile关键字用于声明一个变量,确保多个线程对该变量的访问是可见的和原子的。当一个变量被声明为volatile后,Java内存模型会确保所有使用该变量的线程能看到相同的、一致的值。使用volatile关键字声明变量时,编译器对访问该变量的代码不再进行优化,这样可以提供对变量的稳定访问。未使用volatile修饰的变量,编译器可能会优化读取和存储,可能会使用寄存器中的值,导致不一致的现象。而使用volatile关键字能够保证直接访问内存,避免不一致问题的发生。总结起来,volatile关键字用法是确保多线程环境下对共享变量的可见性和原子性,避免出现线程间的数据不一致问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [java volatile关键字使用方法及注意事项](https://download.youkuaiyun.com/download/weixin_38697940/12775945)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [volatile 关键字的使用](https://blog.youkuaiyun.com/u011116085/article/details/129090784)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值