打印
[LKS32 软件]

volatile定义的作用

[复制链接]
749|43
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
janewood|  楼主 | 2024-11-24 11:37 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

1、为什么用volatile??

volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。声明时语法:

volatile 类型名 变量名

当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。例如:

volatile int i=10;int a = i;...// 中间为其他代码,并未明确告诉编译器,对 i 进行过操作int b = i;
volatile 指出 i 是随时可能发生变化的,每次使用它的时候必须从 i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在 b 中。而优化做法是,由于编译器发现两次从 i读数据的代码之间的代码没有对 i 进行过操作,它会自动把上次读的数据放在 b 中。而不是重新从 i 里面读。这样以来,如果 i是一个寄存器变量或者表示一个端口数据就容易出错,所以说 volatile 可以保证对特殊地址的稳定访问。注意,在 VC 6 中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。下面通过插入汇编代码,测试有无 volatile 关键字,对程序最终代码的影响:

#include <stdio.h>void main(){    int i = 10;    int a = i;    printf("i = %d", a);    // 下面汇编语句的作用就是改变内存中 i 的值    // 但是又不让编译器知道    __asm {        mov dword ptr [ebp-4], 20h    }    int b = i;    printf("i = %d", b);}
在 Debug 版本模式运行程序,输出结果如下:

i = 10i = 32
在 Release 版本模式运行程序,输出结果如下:

i = 10i = 10
输出的结果明显表明,Release 模式下,编译器对代码进行了优化,第二次没有输出正确的 i 值。下面,我们把 i 的声明加上 volatile 关键字,看看有什么变化:

#include <stdio.h>void main(){    volatile int i = 10;    int a = i;    printf("i = %d", a);    __asm {        mov dword ptr [ebp-4], 20h    }    int b = i;    printf("i = %d", b);}
分别在 Debug 和 Release 版本运行程序,输出都是:

i = 10i = 32
这说明这个 volatile 关键字发挥了它的作用。其实不只是“内嵌汇编操纵栈”

这种方式属于编译无法识别的变量改变,另外更多的可能是多线程并发访问共享变量时,一个线程改变了变量的值,怎样让改变后的值对其它线程可见。一般说来,volatile用在如下的几个地方:

中断服务程序中修改的供其它程序检测的变量需要加volatile
多任务环境下各任务间共享的标志应该加volatile
存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义
2、volatile 指针

和 const 修饰词类似,const 有常量指针和指针常量的说法,volatile 也有相应的概念:

修饰由指针指向的对象、数据是 const 或 volatile 的:
const char* cpch;volatile char* vpch;
**注意**:对于 VC,这个特性实现在 VC 8 之后才是安全的。

指针自身的值——一个代表地址的整数变量,是 const 或 volatile 的:
char* const pchc;char* volatile pchv;
**注意**:

可以把一个非volatile int赋给volatile int,但是不能把非volatile对象赋给一个volatile对象。
除了基本类型外,对用户定义类型也可以用volatile类型进行修饰
C++中一个有volatile标识符的类只能访问它接口的子集,一个由类的实现者控制的子集。用户只能用const_cast来获得对类型接口的完全访问。此外,volatile像const一样会从类传递到它的成员。
3. 多线程下的volatile

有些变量是用volatile关键字声明的。当两个线程都要用到某一个变量且该变量的值会被改变时,应该用volatile声明,该关键字的作用是防止优化编译器把变量从内存装入CPU寄存器中。如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。volatile的意思是让编译器每次操作该变量时一定要从内存中真正取出,而不是使用已经存在寄存器中的值,如下:

volatile BOOL bStop = FALSE;
(1)在一个线程中:

while( !bStop ) { ... } bStop = FALSE; return;
(2)在另外一个线程中,要终止上面的线程循环:

bStop = TRUE; while( bStop ); //等待上面的线程终止,如果bStop不使用volatile申明,那么这个循环将是一个死循环,因为bStop已经读取到了寄存器中,寄存器中bStop的值永远不会变成FALSE,加上volatile,程序在执行时,每次均从内存中读出bStop的值,就不会死循环了。
这个关键字是用来设定某个对象的存储位置在内存中,而不是寄存器中。因为一般的对象编译器可能会将其的拷贝放在寄存器中用以加快指令的执行速度,例如下段代码中:

... int nMyCounter = 0; for(; nMyCounter<100;nMyCounter++) { ... } ...
在此段代码中,nMyCounter的拷贝可能存放到某个寄存器中(循环中,对nMyCounter的测试及操作总是对此寄存器中的值进行),但是另外又有段代码执行了这样的操作:nMyCounter -= 1;这个操作中,对nMyCounter的改变是对内存中的nMyCounter进行操作,于是出现了这样一个现象:nMyCounter的改变不同步。

cut-off

volatile对应的变量可能在你的程序本身不知道的情况下发生改变。比如多线程的程序,共同访问的内存当中,多个程序都可以操纵这个变量 。你自己的程序,是无法判定合适这个变量会发生变化 。还比如,他和一个外部设备的某个状态对应,当外部设备发生操作的时候,通过驱动程序和中断事件,系统改变了这个变量的数值,而你的程序并不知道。

对于volatile类型的变量,系统每次用到他的时候都是直接从对应的内存当中提取,而不会利用cache当中的原有数值,以适应它的未知何时会发生的变化,系统对这种变量的处理不会做优化——显然也是因为它的数值随时都可能变化的情况。

使用特权

评论回复
沙发
ccook11| | 2024-12-2 21:33 | 只看该作者
嵌入式编程中,经常需要直接操作硬件寄存器来控制硬件设备。这些寄存器的值可能会因为外部事件(如中断服务程序、硬件定时器等)而改变,而不是通过程序中的普通代码。

使用特权

评论回复
板凳
usysm| | 2024-12-2 22:20 | 只看该作者
一个全局变量用于记录某个设备的状态,主程序和中断服务程序都可能会修改这个变量。在这种情况下,将变量声明为volatile可以确保每个任务或线程都能获取到变量的最新值。

使用特权

评论回复
地板
geraldbetty| | 2024-12-3 08:04 | 只看该作者
在多任务或多线程环境中,不同的线程可能会访问和修改相同的变量。

使用特权

评论回复
5
albertaabbot| | 2024-12-6 19:59 | 只看该作者
当单片机通过指针访问外部硬件设备的寄存器时,由于这些寄存器的值可能会由硬件自动更新(例如,一个定时器寄存器的值会随时间变化),因此应该将这些寄存器对应的变量声明为volatile。这样,每次访问这些变量时,都会直接从硬件寄存器中读取值,而不是使用可能已过时的缓存值。

使用特权

评论回复
6
yeates333| | 2024-12-6 23:05 | 只看该作者
在使用volatile时,需要仔细考虑其必要性和影响范围,避免不必要的性能开销和潜在的问题。

使用特权

评论回复
7
zerorobert| | 2024-12-7 02:08 | 只看该作者
在单片机编程中,经常需要直接访问特定的硬件寄存器。使用volatile关键字可以确保对这些寄存器的访问是稳定的,不会因为编译器的优化而导致意外的行为。

使用特权

评论回复
8
hudi008| | 2024-12-7 05:48 | 只看该作者
单片机中,中断服务程序可能会修改某些全局变量的值。为了确保主程序能够正确读取这些变量的最新值,应该将这些变量声明为volatile。这样,当主程序访问这些变量时,它会知道这些值可能已经被中断服务程序更改,并会直接从内存中读取最新的值。

使用特权

评论回复
9
wilhelmina2| | 2024-12-7 08:54 | 只看该作者
编译器不能对该变量进行某些优化,必须每次都从变量的内存地址中读取其实际的值,而不能使用之前存储在寄存器中的备份值。

使用特权

评论回复
10
maqianqu| | 2024-12-9 09:43 | 只看该作者
编译器在生成代码时,不会对该变量进行优化,即不会将该变量的值存储在寄存器中,也不会假设该变量的值在两次访问之间保持不变。

使用特权

评论回复
11
earlmax| | 2024-12-9 12:48 | 只看该作者
在中断服务程序(ISR)中修改的变量,在主程序中也可能被访问。

使用特权

评论回复
12
mollylawrence| | 2024-12-9 15:51 | 只看该作者
在中断服务例程中使用的变量,特别是那些可能在中断处理过程中被修改的变量,应该声明为 volatile。

使用特权

评论回复
13
MYWX| | 2024-12-10 09:10 | 只看该作者
内存可见性:volatile 确保变量的读写操作对所有线程都是可见的。当一个变量被声明为 volatile 时,每次访问该变量时都会从内存中读取,而不是从CPU缓存中读取,确保获取到的是最新的值。

防止指令重排:volatile 可以防止编译器对包含 volatile 变量的代码进行指令重排。这是因为编译器需要保证每次访问 volatile 变量时都能看到最新的值,所以不能将对 volatile 变量的读写操作与其他操作重排。

多线程编程:在多线程环境中,volatile 可以用来确保一个线程对共享变量的修改能够立即被其他线程看到,从而避免数据竞争和不一致性问题。

硬件寄存器访问:在嵌入式编程中,volatile 通常用于访问硬件寄存器,因为硬件寄存器的值可能会因为外部硬件的行为而改变。

中断服务例程(ISR):在中断服务例程中,可能会修改全局变量,这些变量可能会被主程序访问。将这些变量声明为 volatile 可以确保主程序能够看到中断服务例程中的最新修改。

需要注意的是,volatile 并不能保证复合操作(如递增操作 ++i)的原子性。如果需要保证复合操作的原子性,应该使用同步机制,如互斥锁(mutexes)或原子操作(atomic operations)。此外,过度使用 volatile 可能会导致性能下降,因为它会阻止编译器进行一些优化。因此,volatile 应该只在确实需要的时候使用。

使用特权

评论回复
14
jtracy3| | 2024-12-10 12:53 | 只看该作者
volatile关键字在单片机编程中扮演着重要的角色,它确保了对特定变量的访问不会被编译器优化掉,从而保证了程序的正确性和稳定性。

使用特权

评论回复
15
51xlf| | 2024-12-10 13:22 | 只看该作者
在多线程或并发环境中,多个任务可能会共享同一个变量。如果一个任务在修改这个变量的同时,另一个任务也在读取它,那么就需要使用volatile来确保数据的一致性。因为编译器可能会对非volatile变量进行优化处理,导致在一个任务中读取到的值是旧的或不正确的。

使用特权

评论回复
16
robincotton| | 2024-12-10 15:27 | 只看该作者
编译器通常会尝试优化代码以提高效率,但这可能会导致对volatile变量的不正确处理。使用volatile关键字可以防止编译器对这些变量进行优化,确保每次读取都是从内存中获取最新的值。

使用特权

评论回复
17
hilahope| | 2024-12-10 18:50 | 只看该作者
volatile关键字的作用是告知编译器,被它修饰的变量可能会在程序的控制之外被改变。

使用特权

评论回复
18
backlugin| | 2024-12-10 19:27 | 只看该作者
告诉编译器不要对这个变量进行优化。编译器通常会做一些优化,比如将变量的值缓存到寄存器中,或者假设在一个没有明显赋值操作的循环中变量的值不会改变。如果变量被声明为 volatile,编译器将不会做出这样的假设,每次访问变量时都会直接从内存中读取它的值。

使用特权

评论回复
19
dspmana| | 2024-12-11 17:34 | 只看该作者
当访问单片机的硬件寄存器时,通常需要使用 volatile 关键字,因为这些寄存器的值可能会被硬件事件(如中断、定时器溢出等)修改。

使用特权

评论回复
20
wengh2016| | 2024-12-11 18:04 | 只看该作者
在多线程或多任务环境中,当多个任务共享某个变量时,为了确保数据的实时性和一致性,应该将该变量声明为 volatile。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

58

主题

1301

帖子

1

粉丝