【C语言】volatile 防止编译的时候被优化

volatile 易变的
volatile是 C 和 C++ 中的一个类型修饰符,用于指示编译器该变量可能在程序之外被更改,因此不应对其进行优化。这在涉及硬件寄存器、信号处理或多线程编程时非常有用。

如果你做过单片机开发,你肯定写过这样的代码:延时函数。就是通过循环延长CPU的时间,常见于流水灯、蜂鸣器这些对时间精度要求不高的地方,但是这样的代码却存在着很严重的缺陷,如果编译的过程中开启了优化,有些编译器会认为这几行代码是废话,什么事情也没干,在寄存器中会跳过这部分代码。

于是,在C语言中有了一个很重要的关键字volatile,它的作用是防止编译的时候被优化,在变量之前加上volatile,编译的时候即便开启最高等级的优化gcc test.c -o test -03,原代码中的编译将不再遭到优化处理。volatile在嵌入式开发中非常的重要而且常用,尤其是操作硬件或者多线程的场景。因为编译器在很多时候为了提升程序的运行效率,会跳过和优化掉一些没用的代码,或者直接从寄存器中或者缓存中读取某些变量的值,因为从寄存器或者缓存中取值速度要比内存快,如果这个变量是个共享数据,或者有可能被中断等程序修改,那么,我们读取到的数据就有可能跟内存中的数据不一致。如果在编译中发生这样的事,接下来程序执行的过程中就会导致一系列的问题。

请添加图片描述
请添加图片描述

int a = 10; // 在内存占用4个字节空间,该空间取名为a,cpu将10存入内存a空间中

int b = a;  // 在内存占用4个字节空间,该空间取名为b,cpu将内存a的值取出来,保存在cpu寄存器中(寄存器a)
            // 将寄存器a 赋值给 内存b空间 
int c = a;  // 在内存占用4个字节空间,该空间取名为c,将寄存器a 赋值给 内存c空间
int d = a;  // 在内存占用4个字节空间,该空间取名为d,将寄存器a 赋值给 内存d空间
int e = a;  // 在内存占用4个字节空间,该空间取名为e,将寄存器a 赋值给 内存e空间

// 假如 在int c = a的时候,不惊动cpu 去修改了内存中a的值,那么我们期望 d 和 e 的值 要和修改后a 的值 一致
// 但是 d 和 e 中依旧存入的是在 寄存器a 的值,造成 d 和 e 的值达不到我们的期望 

// 更改方式:(初始化 int a 更改为 volatile)
volatile int a = 10;  //防止CPU对a变量进行优化,从此以后只要用到a的值,就必须去内存中重新读取  —— 每次必须区内存中拿,不去CPU中拿

加上volatile就是告诉编译器这个变量是不稳定的,无论编译器怎么优化,每次都得从内存中去读取,

请添加图片描述

volatile的用途:

  • 硬件寄存器访问:
    • 直接访问:确保每次访问硬件寄存器时都使用 volatile,以避免编译器优化。因为访问硬件设备的寄存器时,寄存器的值可能会在程序之外改变。
	volatile uint32_t *timer_register = (uint32_t *)0x40000000;
	*timer_register = 0x01;
  • 信号处理/中断处理:
    • 标志变量:将中断服务程序(ISR)中使用的标志变量声明为 volatile。
    • 在信号处理程序中使用的变量,因为信号可以在任何时候中断程序执行。
	volatile int interrupt_flag = 0;

	void ISR_Handler() {
    	interrupt_flag = 1;
	}
  • 多线程和共享变量:
    • 共享状态:在多线程应用中,声明那些在不同线程间共享且不受锁保护的变量为 volatile。
    • 在多线程环境中,一个线程可能会修改另一个线程正在读取的变量。
	volatile bool data_ready = false;

示例:

	#include <stdint.h>
	#include <stdbool.h>

	volatile bool data_ready = false;
	volatile uint32_t *adc_result = (uint32_t *)0x40001000;

	void ADC_ISR() {
    	*adc_result = // 读取ADC值
    	data_ready = true;
	}

	int main() {
    	while (1) {
        	if (data_ready) {
            	// 处理ADC结果
            	data_ready = false;
        	}
    	}
    	return 0;
	}

通过正确使用 volatile,可以确保嵌入式系统中硬件和软件的正确交互。

简单的说,volatile 是禁止编译器对该变量的访问进行优化(如缓存、寄存器存储),每次访问该变量时,都会从内存中读取或写入。


使用的时候需要注意的事项:

  1. 仅用于必要情况:不要滥用volatile,只在有硬件交互或异步变化时使用。
  2. 结合同步机制:volatile不能替代互斥锁、信号量等同步机制,需要与其他同步机制(如互斥锁、原子操作)结合使用以确保多线程安全。
  3. 读写原子性:volatile不能保证读写操作的原子性,需要时使用原子操作或禁用中断。
  4. volatile 不能保证线程安全,它仅防止编译器优化。

以上。

我是一个十分热爱技术的程序员,希望这篇文章能够对您有帮助,也希望认识更多热爱程序开发的小伙伴。
感谢!

### 使用C语言联合体访问硬件寄存器 在嵌入式系统编程中,经常需要通过特定的地址空间直接读写硬件寄存器。这通常涉及到对内存映射I/O区域的操作。使用`union`可以帮助程序员更方便地处理不同宽度的数据表示形式。 考虑一个场景,在该场景下有一个32位宽的控制状态寄存器CSR(Control Status Register),它位于物理地址0x4000_0000处。此寄存器具有如下特性: - 寄存器最低有效字节LSB用于指示设备是否准备好; - 中间两个字节用来设置波特率参数; - 最高有效字节MSB保留未定义; 下面展示了一个可能实现方式的例子[^1]: ```c #include <stdint.h> #include <stddef.h> // 定义寄存器结构体 typedef volatile union { uint32_t raw; struct __attribute__((packed)) { uint8_t ready : 1; // 设备就绪标志位 uint8_t reserved : 7; // 剩余部分填充为预留字段 uint16_t baud_rate; // 波特率配置 uint8_t unused; // 高一字节无意义 }; } CSR_REG; #define CSR_ADDR ((uintptr_t)0x40000000) int main(void){ // 创建指向实际寄存器位置的指针变量 CSR_REG *csr_ptr = (CSR_REG *)CSR_ADDR; while (!(*csr_ptr).ready){ /* 等待直到设备准备完毕 */ } (*csr_ptr).baud_rate = 9600; // 设置串口通信速率 return 0; } ``` 上述代码片段展示了如何创建一个名为`CSR_REG`的联合类型,并将其成员之一解释成整个32位整数(`raw`),另一个则被解析为由几个独立域组成的匿名结构体。这样做的好处是可以轻松切换查看或修改寄存器的不同方面而不必担心越界问题。同时声明为volatile确保编译器不会优化掉对外部存储器的实际访问操作。 此外,值得注意的是这里采用了GNU C扩展属性`__attribute__((packed))`来防止编译器自动调整结构体内存排列顺序从而影响到最终生成的目标文件与真实硬件之间的兼容性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猫猫的小茶馆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值