volatile修饰变量

volatile 影响编译器编译的结果,指出,volatile 变量是随时可能发生变化的,与volatile变量有关的运算,不要进行编译优化,以免出错,(VC++ 在产生release版可执行码时会进行编译优化,加volatile关键字的变量有关的运算,将不进行编译优化。)。

例如:

volatile int i=10;

int j = i;

...

int k = i;

volatile 告诉编译器i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的可执行码会重新从i的地址读取数据放在k中。

而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在k中。而不是重新从i里面读。这样以来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问,不会出错。

/**********************

一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:

1) 并行设备的硬件寄存器(如:状态寄存器)

2) 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)

3) 多线程应用中被几个任务共享的变量

我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道,所有这些都要求用到volatile变量。不懂得volatile的内容将会带来灾难。假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。

1)一个参数既可以是const还可以是volatile吗?解释为什么。

2); 一个指针可以是volatile 吗?解释为什么。

3); 下面的函数有什么错误:

int square(volatile int *ptr)

{

return *ptr * *ptr;

}

下面是答案:

1)是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。

2)是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。

3) 这段代码有点变态。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:

int square(volatile int *ptr)

{

int a,b;

a = *ptr;

b = *ptr;

return a * b;

}

由于*ptr的值可能被意想不到地该变,因此ab可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:

long square(volatile int *ptr)

{

int a;

a = *ptr;

return a * a;

}

位操作(Bit manipulation

//*********************

嵌入式编程中经常用到 volatile这个关键字,在网上查了下他的用法可以归结为以下两点:

一:告诉compiler不能做任何优化

比如要往某一地址送两指令:

int *ip =...; //设备地址

*ip = 1; //第一个指令

*ip = 2; //第二个指令

以上程序compiler可能做优化而成:

int *ip = ...;

*ip = 2;

结果第一个指令丢失。如果用volatilecompiler就不允许做任何的优化,从而保证程序的原意:

volatile int *ip = ...;

*ip = 1;

*ip = 2;

即使你要compiler做优化,它也不会把两次付值语句间化为一。它只能做其它的优化。这对device driver程序员很有用。

二:表示用volatile定义的变量会在程序外被改变,每次都必须从内存中读取,而不能把他放在cache或寄存器中重复使用。

volatile char a;  

a=0;

while(!a){

//do some things;  

}  

doother();

如果没有 volatile doother()不会被执行。

原文:

http://www.cnblogs.com/chio/archive/2007/11/24/970632.html

<think>好的,我现在需要回答用户关于volatile关键字使用情境的问题。用户已经知道volatile用于变量可能被意外改变的情况,现在想知道具体有哪些情境会出现这种情况。我需要整理出常见的应用场景,并给出具体的例子和解释。 首先,回顾一下volatile的作用:告诉编译器不要优化这个变量,每次访问都从内存中读取或写入,而不是使用寄存器中的缓存值。这样做的目的是当变量被外部因素修改时,程序能正确读取最新的值。 接下来,思考哪些情况下变量会被外部因素意外修改。根据之前的回答,可能包括硬件寄存器、中断服务程序中的共享变量、多线程环境、内存映射I/O、DMA传输、信号处理程序、看门狗定时器等。 需要将这些场景逐一列举,并给出具体的例子。例如,硬件寄存器通常用于嵌入式系统中与外设通信,如读取状态寄存器或配置控制寄存器。这时候必须用volatile,因为寄存器的值可能由硬件自动改变,而不是程序本身。 中断服务程序中的共享变量也是一个典型例子。主循环可能在读取一个变量,而中断发生时,ISR会修改这个变量。如果没有volatile,编译器可能会优化掉重复读取,导致主循环看不到变量的变化。 多线程环境中的共享变量,虽然现代编译器对多线程有一定的意识,但某些情况下仍需volatile,尤其是在没有使用互斥锁或原子操作时。不过要注意,volatile并不能替代同步机制,只是确保每次访问都从内存读取。 内存映射I/O,比如通过指针访问特定内存地址,这些地址可能映射到外设的寄存器,同样需要volatile来防止编译器优化访问。 DMA传输时,内存区域可能被DMA控制器直接修改,而CPU可能缓存了这部分内存的数据,因此需要使用volatile,并结合Cache控制来确保数据一致性。 信号处理程序中,主程序可能被信号中断,处理函数修改的变量需要用volatile,以确保主程序能正确看到变化。 看门狗定时器的计数器,可能由硬件自动递减,程序需要定期写入以防止复位,此时变量必须声明为volatile,避免编译器优化掉必要的写入操作。 需要注意,每个例子都要具体说明为什么volatile是必要的,可能产生的错误情况,以及如何避免。同时,可能需要指出volatile的局限性,比如在多线程中不能替代同步机制,需要结合其他手段。 另外,用户可能对某些场景不太熟悉,比如内存映射I/O或DMA,需要用简明的语言解释。确保例子准确,避免技术错误,比如正确的语法示例。 最后,总结这些情境,并强调正确使用volatile的重要性,以及错误使用可能导致的后果。可能还需要提到一些常见的误用情况,提醒用户注意。</think>在嵌入式系统和底层编程中,`volatile`关键字用于标记那些可能被“外部因素”意外修改的变量。以下是需要`volatile`的典型场景及具体原因: --- ### 一、硬件寄存器访问(Memory-Mapped I/O) **场景**:通过内存地址直接访问硬件外设的寄存器(如GPIO状态、UART数据寄存器)。 **问题**:寄存器的值由硬件自动更新,但编译器可能误认为代码逻辑没有修改该变量,从而优化掉“冗余”的读取操作。 **示例**: ```c volatile uint32_t *status_reg = (uint32_t*)0x40000000; // 硬件状态寄存器地址 // 轮询等待状态位就绪 while ((*status_reg & 0x01) == 0) { // 每次循环必须重新读取寄存器值 // 等待... } ``` **未用`volatile`的后果**:编译器可能将`*status_reg`缓存在寄存器中,导致死循环(硬件状态变化无法被检测)。 --- ### 二、中断服务程序(ISR)共享变量 **场景**:主程序与中断服务程序(ISR)共享的变量(如标志位、计数器)。 **问题**:ISR可能在任意时刻修改变量,但编译器不知道这种“异步修改”,可能优化掉对变量的重复读取。 **示例**: ```c volatile bool data_ready = false; // 中断触发的数据就绪标志 // 主程序 while (!data_ready) { // 必须每次从内存读取最新值 // 等待中断触发... } // 中断服务程序 void ISR() { data_ready = true; // 中断异步修改标志 } ``` **未用`volatile`的后果**:编译器可能将`data_ready`优化为只读一次,导致主程序永远无法退出循环。 --- ### 三、多线程共享变量(需谨慎!) **场景**:多线程环境下,多个线程访问的共享变量(如全局状态)。 **问题**:线程可能在另一个线程中被修改,但编译器可能优化掉必要的内存访问。 ```c volatile int shared_counter = 0; // 线程A void threadA() { shared_counter++; // 其他线程可能同时修改 } // 线程B void threadB() { printf("%d", shared_counter); // 需看到最新值 } ``` **注意**: - `volatile`仅保证每次访问从内存读写,但**不保证原子性**(如`shared_counter++`可能被中断,需配合锁或原子操作)。 - 现代C/C++标准中,更推荐使用`std::atomic`或互斥锁(如`pthread_mutex`)实现线程安全。 --- ### 四、内存被外部设备修改(DMA、协处理器) **场景**:DMA控制器或协处理器直接修改某块内存(如网络数据接收缓冲区)。 **问题**:内存内容可能被外部设备更新,但CPU缓存中可能保留旧值。 **示例**: ```c volatile uint8_t dma_buffer[1024]; // DMA传输的目标缓冲区 // 启动DMA传输后,等待数据就绪 while (dma_status != COMPLETE) { // 必须每次读取最新状态 } // 使用dma_buffer中的数据(可能已被DMA修改) ``` **扩展问题**: - 若内存区域被缓存(Cache),还需通过`Cache无效化`操作确保CPU读取到最新数据(`volatile`本身不处理Cache一致性)。 --- ### 五、信号处理程序中的变量 **场景**:信号处理函数(如`SIGINT`)修改全局变量,主程序检测该变量。 **问题**:编译器可能优化掉对变量的访问,导致信号处理函数的修改对主程序不可见。 **示例**: ```c #include <signal.h> volatile sig_atomic_t exit_flag = 0; void sig_handler(int sig) { exit_flag = 1; // 信号处理函数修改标志 } int main() { signal(SIGINT, sig_handler); while (!exit_flag) { // 必须每次检测最新值 // 主循环... } return 0; } ``` --- ### 六、看门狗定时器(Watchdog Timer) **场景**:定期重置看门狗计数器以防止系统复位。 **问题**:编译器可能将重复写入操作优化为单次写入,导致看门狗超时。 **示例**: ```c volatile uint32_t *wdt_counter = (uint32_t*)0xFFFF0000; void feed_watchdog() { *wdt_counter = 0xAA55; // 必须每次实际写入硬件 } ``` --- ### 七、动态内存映射(Memory Re-mapping) **场景**:同一内存地址在不同时刻映射到不同物理位置(如内存切换技术)。 **问题**:编译器可能缓存旧地址对应的数据,未察觉内存映射已变化。 **示例**: ```c volatile uint8_t *config_page = (uint8_t*)CONFIG_BASE; void switch_config_page(int page) { remap_memory(CONFIG_BASE, page); // 切换内存映射 config_page[0] = 0x01; // 必须写入新映射的内存 } ``` --- ### 总结:何时使用`volatile`? 1. **变量可能被硬件自动修改**(如硬件寄存器)。 2. **变量被异步上下文修改**(如中断、信号处理程序)。 3. **变量所在内存被外部设备直接修改**(如DMA缓冲区)。 4. **变量在多线程中共享且无锁保护**(需谨慎,通常需结合原子操作)。 --- ### 常见误区 - **误将`volatile`用于多线程同步**: `volatile`不保证操作的原子性,也无法替代锁或内存屏障(如`std::mutex`或`std::atomic`)。 - **过度使用`volatile`**: 非必要场景使用`volatile`会抑制编译器优化,降低性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值