volatile

语义

volatile

Indicates that a variable can be changed by a background routine.

Keyword volatile is an extreme opposite of const. It indicates that a variable may be changed in a way which is absolutely unpredictable by analysing the normal program flow (for example, a variable which may be changed by an interrupt handler). This keyword uses the following syntax:

volatile data-definition;
Every reference to the variable will reload the contents from memory rather than take advantage of situations where a copy can be in a register.

概括一下: volatile 指变量是易变的,它可能随时被别的程序流修改,被volatile 修饰的变量不能储存在寄存器中。

语法

修饰变量

volatile int a = 0

修饰指针

 int * volatile p;

修饰指针指向的地址

volatile int * p;

修饰指针及指向的地址

volatile int * volatile p;

修饰结构体成员

通过一个non-volatile的结构体指针,去访问被定义为volatile的结构体成员,C标准没有对编译器在这种情况下的行为做规定。可能将其成员作为volatile 变量,也可能作为普通变量。

修饰指向结构体指针

将一个non-volatile的结构体地址赋给一个 volatile的指针,这样对volatile指针所指结构体的使用都会被编译器认为是volatile的,即使原本那个对象没有被声明为 volatile。

实现机制

根据volatile的语义可以理解为: 使用它是为了保证顺序一致性。因此对volatile的实现一般和以下三点有关
1 不会进行编译器优化。
编译器遇到volatile 描述的变量禁止编译器优化,一般优化方式有下面几种:

  • 变量改为寄存器变量
  • 指令省略
  • 指令重排
  • 其他优化

2 cpu不会进行指令乱序和并发

3 修改数据后立即同步各缓存(内存),保证缓存(内存)一致

1、2保证了执行顺序与程序顺序是一致的; 3保证了修改数据对所有共享者是立即可见的。123一起即可保证顺序一致性。C编译器通常可以实现 1,但并不保证2,3。

对比Java的volatile,Jvm实现了123: 参考https://blog.csdn.net/lc13571525583/article/details/90345760

C/C++实现

转自http://baiy.cn/doc/cpp/advanced_topic_about_multicore_and_threading.htm

C/C++ 中的 volatile 关键字提供了以下保证:
  1. 对声明为 volatile 的变量进行的任何操作都不会被优化器去除,即使它看起来没有意义(例如:连续多次对某个变量赋相同的值),因为它可能被某个在编译时未知的外部设备或线程访问。
  2. 被声明为 volatile 的变量不会被编译器优化到寄存器中,每次读写操作都保证在内存(详见下文)中完成。
  3. 在不同表达式内的多个 volatile 变量间的操作顺序不会被优化器调换(即:编译器保证多个 volatile 变量在 sequence point 之间的访问顺序不会被优化和调整)。
volatile *不* 提供如下保证
  1. volatile 声明不保证读写和运算操作的原子性。
  2. volatile 声明不保证对其进行的读写操作直接发生在主内存。相反,CPU 会尽可能让这些读写操作发生在 L1/L2 等 cache 上。除非:
    1. 发生了一个未命中的读请求。
    2. 所有级别的 cache 均已被配置为通过式写(write through)。
    3. 目标地址为 non-cacheable 区(主要是其它设备映射到内存地址空间的通信接口。例如:网卡的板载缓冲区、显卡板载显存、WatchDog 寄存器等等)。
  3. 编译器仅保证在生成目标码时不调整 volatile 变量的访问顺序,但通常并不保证该变量不受处理器的 out-of-order 特性影响。目前唯一一个已知的特例是安腾(IA64)处理器版的 VC:在生成 IA64 Target 时,VC 会自动在所有 volatile 访问之间添加内存屏障(详见下文)以保证访问顺序。但 ISO 标准并未要求编译器实现类似机制。实际上,其它编译器(或是面向其它平台的 VC)也都没有类似保证。也就是说,通常认为 volatile 并不保证代码在处理器上的执行顺序,如果需要类似的保证,程序员应当自己使用内存屏障操作。

原子操作要求做到以下保证:
  • 对原子量的 '读出-计算-写入' 操作序列是原子的,在以上动作完成之前,任何其它处理器和线程均无法访问该原子量。
  • 原子操作必须保证缓存一致性。即:在多处理器环境中,原子操作不仅要把计算结果同步到主存,还要以原子的语义将他们同步到当前平台上其他 CPU 的 cache 中。在大部分硬件平台上,cache 同步通常由锁总线指令完成。意即:逻辑上,所有原子操作都显式使用或隐含使用了一个锁总线操作。例如:x86 指令 'cmpxchg' 中就隐含了锁总线操作。

可见,使用 volitale 关键字并不足以保证操作的原子语义。 volitale 关键字的主要设计目的是支持 C/C++ 程序与内存映射设备间的通信。但这并不是说 volitale 关键字对原子操作没有任何帮助:

  • 对于一个被声明为 volitale 类型的原子量来说,如果所有写操作都是原子的,并且完成了必要的 cache 同步,那么读取该原子量时就不必再次对总线上锁。

    这是因为 volitale 关键字保证了对该变量的读操作起码是在当前 CPU cache 中完成的(即:该变量不会被优化到寄存器中)。与此同时,对该变量的所有写操作都已保证原子地完成了所有 CPU 间的 cache 同步以及主存同步操作。所以读操作不管在主存还是当前系统中任意一个 CPU 的 cache 上发生,读到的都是一致的数据。

    在这里,volitale 关键字配合原子量的写入操作一起实现了一个典型的读者/写者同步模型:所有写操作和 cache 同步都保证被原子、互斥地完成;读操作则可以被并发地实现。这也是 Windows API 中没有提供类似 'AtomicLoad' 式语义操作的原因
    当然,这里还有一个隐含的附加条件,就是 CPU 必须能够在一个操作中读入这个原子量。这个条件通常可以忽略,因为超出 CPU 位宽的数据类型通常无法被实现成原子量类型。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值