卡码笔记
首页
计算机基础
C++
Java
面经
笔记广场 (opens new window)
代码随想录 (opens new window)
首页
计算机基础
C++
Java
面经
笔记广场 (opens new window)
代码随想录 (opens new window)
  • 基础与语法

    • 介绍c++一下三大特性
    • 指针和引用的区别
    • 结构体和类的区别
    • 结构体与联合体的区别
    • static关键字和const关键字的作用
    • extern C的作用
    • volatile关键字的作用
      • 简要回答
      • 详细回答
      • 代码示例
      • 知识拓展
    • inline函数与宏的区别与优劣
    • auto和decltype的区别
    • sizeof和strlen的区别
    • 浮点数比较方法
    • 静态局部变量,全局变量,局部变量的特点,以及使用场景
    • C++中四种类型转换
  • 面向对象

  • STL 与容器

  • 内存管理

  • C++11 与现代 C++

  • 智能指针

  • 并发与 I/O

# c++中volatile关键字的作用

# 简要回答

volatile是一个类型修饰符,用于告知编译器该变量的值可能会被程序之外的、编译器无法感知的因素(如硬件、中断服务程序、其他线程等)意外修改。

它主要作用是:禁止编译器对该变量进行优化(如缓存到寄存器、消除“冗余”读写操作),确保每次访问变量时都直接从其内存地址中读取或写入。

用了volatile,编译器就会老老实实地每次去读它的真实值,而不会用之前缓存的旧值来替代。

# 详细回答

volatile关键字的核心是解决“变量值的不确定性”问题。

在标准的单线程程序中,编译器通过分析代码流,可以自信地做出很多优化假设。

例如,它认为一个变量在两次读取之间如果没有本线程的写操作,那么它的值就不会改变

从而可能用第一次读取到的缓存值(例如在寄存器中)来代替第二次读取,或者干脆消除掉一些它认为“无用”的读写操作。

然而,在一些特定场景下,变量的值会“莫名其妙”地改变,比如下面这两种场景:

  1. 内存映射硬件寄存器 (Memory-mapped I/O):硬件设备的状态或数据寄存器被映射到特定的内存地址。

    程序通过读写这些内存地址来与硬件交互,这些地址的值会随着硬件状态的变化而改变,完全不受程序控制。

  2. 中断服务例程 (ISR) 修改的全局变量:主程序在读写一个全局变量时,可能被一个中断打断,ISR修改了这个变量的值。

    从主程序的代码流来看,这个变量似乎没有被修改,但它的值确实变了。

volatile的作用就是抑制编译器的优化,具体包括:

禁止编译器将变量缓存在寄存器中,强制每次访问都进行内存操作。

禁止编译器重排与volatile变量相关的指令(注意:这只限制了编译器的重排,CPU的重排仍需靠内存屏障等其他机制阻止)。

保证对volatile变量的读写操作不会被编译器作为“无效操作”而消除。

# 代码示例

场景:读取内存映射的温度传感器寄存器

假设0x4000是一个硬件温度传感器数据寄存器的内存映射地址。

没有 volatile 的问题代码:

uint32_t* temperature_reg = (uint32_t*)0x4000;

void read_temperature() {
    uint32_t temp1 = *temperature_reg; // 第一次读取,编译器可能将值缓存
    // ... 一些不修改 temperature_reg 的代码
    uint32_t temp2 = *temperature_reg; // 第二次读取,编译器可能直接使用缓存的temp1值

    if (temp1 != temp2) {
        // 由于硬件寄存器值可能已变化,我们期望这里可能被执行
        // 但优化后的编译器可能认为temp1永远等于temp2,从而将整个if块消除!
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

编译器优化后可能生成的指令等价于:

uint32_t temp = *temperature_reg;
uint32_t temp2 = temp; // 根本没有第二次真正的内存读取!
if (temp != temp2) { ... } // 条件永远是假的,整个if块被删除
1
2
3

使用 volatile的正确代码:

// 告诉编译器这个指针指向的内容是易变的
volatile uint32_t* temperature_reg = (volatile uint32_t*)0x4000;

void read_temperature() {
    uint32_t temp1 = *temperature_reg; // 强制从地址0x4000读取
    // ... 一些代码
    uint32_t temp2 = *temperature_reg; // 再次强制从地址0x4000读取

    if (temp1 != temp2) {
        // 现在这个比较是真实发生的,代码不会被优化掉
        printf("Temperature changed: %d -> %d\n", temp1, temp2);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

现在编译器会老老实实生成两次内存读取指令,if语句也会被保留。

# 知识拓展

  • volatile与const和atomic之间的重要区别:

volatile vs const:

const表示“程序本身不应修改”,volatile表示“程序之外可能修改”,二者互不冲突,可以同时使用(如const volatile uint32_t* reg_status;)。

volatile vs std::atomic:

这是现代C++中最重要的区别,std::atomic既解决了可见性问题(类似volatile),又解决了操作原子性和内存顺序问题。

在需要与硬件交互或与中断服务例程(ISR)通信时,用volatile;在需要多线程同步时,应首选std::atomic。

  • 知识图解

image

  • 适用场景:

嵌入式系统/驱动程序开发:访问内存映射的硬件设备寄存器(只读、只写或读写)。

与中断服务程序(ISR)通信:在主循环中读取由ISR修改的全局标志位或缓冲区。

在信号处理程序中修改的全局变量(通常结合sig_atomic_t使用)。

在缺乏std::atomic支持的旧式编译器或环境中,用于实现一种脆弱的多线程共享(现代C++强烈不推荐)。

  • 面试官可能追问

Q1: volatile 能保证原子性吗?为什么?

A1: 不能。volatile 只能保证单次读或写操作的原子性。但对于符合操作,如 i++(它等价于 i = i + 1),它包含了读取 i 的值、给 i 加 1、将新值写回 i 三个步骤。

volatile 只能保证这三个步骤作为一个整体在执行时不会被中断(即读到的和写入的是最新值),但并不能防止多个线程交错执行这三个步骤,从而导致最终结果错误。

Q2: volatile和std::atomic有什么区别? A2: 主要区别有三点:

  1. 原子性 (Atomicity):std::atomic保证该类型上的读写操作是原子的,而volatile不保证。

  2. 内存顺序 (Memory Ordering):std::atomic允许开发者指定内存顺序语义(如memory_order_consume),控制指令重排,从而在多核CPU上正确同步线程。

    volatile不提供这种保证,它只阻止编译器重排,不阻止CPU重排。

  3. 主要用途:volatile用于与硬件/异步信号(ISR)交互;std::atomic用于多线程同步。

Last Updated: 3/10/2026, 6:08:48 PM

← extern C的作用 inline函数与宏的区别与优劣 →

评论

验证登录状态...

侧边栏
夜间
卡码简历
代码随想录
卡码投递表🔥
2026群
添加客服微信 PS:通过微信后,请发送姓名-学校-年级-2026实习/校招
支持卡码笔记
鼓励/支持/赞赏Carl
1. 如果感觉本站对你很有帮助,也可以请Carl喝杯奶茶,金额大小不重要,心意已经收下
2. 希望大家都能梦想成真,有好的前程,加油💪