了解 volatile 的用法

本文详细解释了Volatile关键字的由来及其在多线程环境中的作用。通过实例展示了Volatile如何防止编译器优化导致的数据不一致问题,并对比了使用与未使用Volatile时的汇编代码差异。

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

volatile的本意是一般有两种说法--1.“暂态的”;2.“易变的”。

其实Volatile是由于编译器优化所造成的一个Bug而引入的关键字。
int a = 10;
int b = a;
int c = a;
理论上来讲每次使用a的时候都应该从a的地址来读取变量值,但是这存在一个效率问题,就是每次使用a都要去内存中取变量值,然后再通过系统总线传到CPU处理,这样开销会很大。所以那些编译器优化者故作聪明,把a读进CPU的cache里,像上面的代码,假如a在赋值期间没有被改变,就直接从CPU的cache里取a的副本来进行赋值。但是bug也显而易见,当a在赋给b之后,可能a已经被另一个线程改变而重新写回了内存,但这个线程并不知道,依旧按照原来的计划从CPU的cache里读a的副本进来赋值给c,结果不幸发生了。
于是编译器的开发者为了补救这一bug,提供了一个Volatile让开发人员为他们的过失埋单,或者说提供给开发人员了一个选择效率的权利。当变量加上了Volatile时,编译器就老老实实的每次都从内存中读取这个变量值,否则就还按照优化的方案从cache里读。

 

例1.

void main (void)
{
volatile int i;
int j;

i = 1;  //1  不被优化 i=1
i = 2;  //2  不被优化 i=1
i = 3;  //3  不被优化 i=1

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 F500    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 F500    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();


 

<think>首先,用户的问题是:“我想了解C语言中volatile关键字的用法和场景请问C语言volatile关键字使用场景及作用”。这直接询问了volatile关键字的用法、场景和作用。系统级指令要求:-所有行内数学表达式必须使用$...$格式,例如$x^2$。但在这个主题中,可能没有数学表达式,所以如果不需要,就不用添加。-独立公式必须使用$$...$$格式并单独成段。-LaTeX语法正确。-使用中文回答。-生成相关问题。-在回答中引用的段落末尾自然地添加引用标识,例如[^1]。-回答结构清晰,帮助用户逐步解决问题,保证真实可靠。参考站内引用:-引用[1]:深入理解C语言volatile关键字。强调它在代码组织、访问控制和性能优化中的作用。-引用[2]:嵌入式C语言volatile关键字作用。解释它告知编译器变量可能被程序外因素修改,防止编译器优化。-引用[3]:C语言基础:volatile关键字的作用。提到在嵌入式程序中常见,防止代码优化。这些引用提供了背景:volatile关键字用于防止编译器优化变量访问,特别是在嵌入式系统中,变量可能被外部因素如硬件修改。我的回答结构:1.**逐步解决问题**:先解释volatile关键字的基本概念和作用,然后讨论使用场景,最后给出示例或注意事项。2.**结构清晰**:使用标题或分段。3.**使用中文**:整个回答用中文。4.**添加引用**:在相关段落末尾自然添加引用标识,引用上述内容。例如,当提到性能优化时,添加[^1]。5.**生成相关问题**:在回答结束后,添加“相关问题”部分,列出几个相关问题。内容大纲:-**引言**:简要介绍volatile关键字。-**作用**:详细说明它的作用——防止编译器优化,确保每次访问变量都从内存读取。-**使用场景**:列举常见场景,如嵌入式系统、多线程、硬件寄存器访问。-**示例**:给出简单的C语言代码示例。-**注意事项**:提醒用户使用时要注意的事项。-**相关问题**:生成2-3个相关问题。生成相关问题:基于主题,相关问题可以是:1.在嵌入式系统中,volatile关键字的具体应用有哪些?2.使用volatile关键字时如何避免常见的错误?3.volatile关键字与const关键字有什么区别和联系?确保回答真实可靠:基于提供的引用和一般知识。引用是站内的,所以我需要自然地引用它们。添加引用:-当讨论性能优化时,引用[^1]。-当解释嵌入式系统中的作用时,引用[^2]。-当提到防止代码优化时,引用[^3]。最终回答结构:-开头:直接回答问题。-主体:分段解释。-结尾:相关问题。</think>### C语言中volatile关键字的用法和场景 在C语言编程中,`volatile`关键字是一个类型修饰符(Type Qualifier),用于告知编译器:该变量的值可能被程序以外的因素意外修改,因此编译器不应对此变量进行激进的优化。这确保了每次访问该变量时,都会直接从内存中读取或写入最新值,而不是依赖于编译器的缓存或寄存器优化。下面我将逐步解释其作用、使用场景和注意事项,帮助您全面理解。 #### 1. **volatile关键字的作用** - **防止编译器优化**:编译器优化代码时,可能假设变量的值在连续访问中不变,从而省略不必要的读取或写入操作。但`volatile`强制编译器每次访问变量时都生成直接内存操作指令,确保数据一致性。例如,在嵌入式系统中,一个变量可能被硬件中断修改,如果未使用`volatile`,编译器优化可能导致程序读取到过时的值[^2]。 - **确保内存可见性**:在多线程或硬件交互场景中,`volatile`确保变量的修改对所有线程或外部设备立即可见,避免因缓存机制导致的错误[^3]。 - **提升可靠性**:它不改变变量的存储位置,但通过约束编译器优化行为,增强了代码在不确定环境中的鲁棒性。深入理解这一机制,对编写高效和可维护的程序至关重要[^1]。 #### 2. **volatile关键字的使用场景** `volatile`主要应用于变量值可能被外部因素修改的场景,常见于以下情况: - **嵌入式系统**:在微控制器编程中,硬件寄存器(如状态寄存器或I/O端口)的值可能由外部事件(如中断或传感器输入)改变。使用`volatile`声明这些变量,可确保程序读取实时值。例如: ```c volatile uint32_t *status_reg = (volatile uint32_t *)0x40000000; // 硬件地址映射 while (*status_reg & 0x01) { // 每次循环都读取最新状态 // 等待硬件就绪 } ``` 这防止编译器优化循环为无限等待[^2][^3]。 - **多线程环境**:当多个线程共享一个变量时(例如标志位),`volatile`确保一个线程的修改能被其他线程立即看到。但注意,`volatile`不提供原子性(atomicity),需结合锁机制使用。 ```c volatile int flag = 0; // 共享标志变量 // 线程1修改flag flag = 1; // 线程2读取flag if (flag == 1) { // 执行操作 } ``` - **内存映射I/O**:在驱动程序开发中,变量映射到硬件设备的物理地址。`volatile`确保每次访问都直接与硬件交互,避免编译器优化导致操作丢失。 - **信号处理程序**:在信号处理器(signal handler)中修改的全局变量应声明为`volatile`,以防止主程序使用缓存值。 #### 3. **注意事项和最佳实践** - **避免滥用**:`volatile`会增加内存访问开销,降低性能。只在必要场景使用,例如当变量可能被硬件或异步事件修改时[^1][^2]。 - **与const结合**:`volatile`可与`const`一起使用(如`const volatile`),表示变量值不能被程序修改,但可能被外部改变(如只读硬件寄存器)。 - **不保证原子性**:`volatile`仅解决可见性问题,不提供多线程安全。在高并发场景中,应使用原子操作或互斥锁。 - **编译器依赖**:不同编译器对`volatile`的实现可能略有差异,在跨平台开发中需测试验证。 总之,`volatile`关键字在嵌入式程序设计和系统级编程中扮演核心角色,能有效处理外部不确定性。通过合理应用,您可以避免许多隐蔽的bug[^1][^2][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值