C++中volatile的具体含义和可能得坑

这篇具有很好参考价值的文章主要介绍了C++中volatile的具体含义和可能得坑。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

  似乎很多人不理解voliate和atomic啥区别,本文主要主要描述volatile的作用和使用场景。对比了atomic和volatile的区别,以及性能差异。最后补充了几条可能导致C++代码测试volatile导致正确结果错误结论的依据。

1 volatile

1.1 c++标准中对于volatile的定义

  Every access (read or write operation, member function call, etc.) made through a glvalue expression of volatile-qualified type is treated as a visible side-effect for the purposes of optimization (that is, within a single thread of execution, volatile accesses cannot be optimized out or reordered with another visible side effect that is sequenced-before or sequenced-after the volatile access. This makes volatile objects suitable for communication with a signal handler, but not with another thread of execution, see std::memory_order). Any attempt to access a volatile object through a glvalue of non-volatile type (e.g. through a reference or pointer to non-volatile type) results in undefined behavior.

  CPP Reference中说的很清楚volatile的作用有两个:

  1. 提醒编译器不要对该变量相关的代码进行优化,避免出现意外的负面作用;
  2. 对类似的表达式不进行编译层面的指令重排。编译指令重排也是一种编译器优化手段,这条严格来说也是第一条的变种。

  下面看一段比较简单的代码:

std::atomic<int> i = 0;
volatile int j = 0;
int g = 0;
int n = 100000000;

void func() {
    for (int a = 0; a < n; a++) {
        i++;
    }
}
//func对应的汇编
//00AB1010  mov         eax,5F5E100h  
//00AB1015  lock inc    dword ptr [i (0AB53FCh)]  
//00AB101C  sub         eax,1  
//00AB101F  jne         func+5h (0AB1015h)  

void func1() {
    for (int a = 0; a < n; a++) {
        j++;
    }
}
//func1 对应的汇编
//00AB1030  mov         eax,5F5E100h  
//00AB1035  nop         word ptr [eax+eax]  
//00AB1040  mov         ecx,dword ptr [j (0AB53F8h)]  
//00AB1046  inc         ecx  
//00AB1047  mov         dword ptr [j (0AB53F8h)],ecx  
//00AB104D  sub         eax,1  
//00AB1050  jne         func1+10h (0AB1040h) 


void func2() {
    for (int a = 0; a < n; a++) {
        g++;
    }
}
//func2对应的汇编
//add         dword ptr [g (0AB5400h)],5F5E100h

  上面的代码中可以看到对于普通变量的++直接被编译器在编译期间就分析出来结果,直接赋值了,for循环直接被干掉了。为了避免编译器在编译期间计算出结果,我们加一行printf。

void func1() {
    for (int a = 0; a < n; a++) {
        j++;
        printf("");
    }
}
//00CD1071  mov         esi,5F5E100h  
//00CD1076  nop         word ptr [eax+eax]  
//00CD1080  mov         eax,dword ptr [j (0CD53F0h)]  
//00CD1085  inc         eax  
//00CD1086  push        offset string "" (0CD31C8h)  
//00CD108B  mov         dword ptr [j (0CD53F0h)],eax  
//00CD1090  call        printf (0CD1010h)  
//00CD1095  add         esp,4  
//00CD1098  sub         esi,1  
//00CD109B  jne         func1+10h (0CD1080h)

void func2() {
    for (int a = 0; a < n; a++) {
        g++;
        printf("");
    }
}

//    for (int a = 0; a < n; a++) {
//00CD10A1  mov         esi,5F5E100h  
//00CD10A6  nop         word ptr [eax+eax]  
//00CD10B0  inc         dword ptr [g (0CD53F8h)]  
//00CD10B6  push        offset string "" (0CD31C8h)  
//00CD10BB  call        printf (0CD1010h)  
//00CD10C0  add         esp,4  
//00CD10C3  sub         esi,1  
//00CD10C6  jne         func2+10h (0CD10B0h)

  可以看到普通类型编译的代码直接操作的内存,而volatile修饰的代码和没有开优化的代码一样。
  顺带贴一段llvm中关于volatile的描述。

  If R is volatile, the result is target-dependent. (Volatile is supposed to give guarantees which can support sig_atomic_t in C/C++, and may be used for accesses to addresses that do not behave like normal memory. It does not generally provide cross-thread synchronization.

1.2 volatile的实际应用

  volatile主要应用是和硬件打交道的代码中会用到,比如嵌入式等,另外,还有cppreference中提到了信号处理程序。比如下面的代码就是一个简单的定时器代码,Timer0Handler是时钟0的终端事件,程序的行为预期是经过一段时间即50个小时,程序退出。

uint32_t volatile times= 0;
void main(){
	//时钟初始化
	while(times < 50 * 60 * 60){
    }
}
void Timer0Handler() interrupt 1{
	++times;
}

  如果times不加volatile就会被编译器常量优化,导致while变成死循环。

2 volatile和atomic

  先说结论volatile和atomic没有任何关系。volatile不保证原子性。

2.1 msvc的扩展

  msvc对于volatile的实现不符合标准,自己扩展了volatile的语义。msvc提供了两种volatile,分别为/volatile:iso/volatile:ms,其中x86机器上后者为默认,arm上前者为默认,详情参考msvc-volatile

  • volatile:Selects strict volatile semantics as defined by the ISO-standard C++ language. Acquire/release semantics are not guaranteed on volatile accesses. If the compiler targets ARM (except ARM64EC), this is the default interpretation of volatile.
  • volatile:ms:Selects Microsoft extended volatile semantics, which add memory ordering guarantees beyond the ISO-standard C++ language. Acquire/release semantics are guaranteed on volatile accesses. However, this option also forces the compiler to generate hardware memory barriers, which might add significant overhead on ARM and other weak memory-ordering architectures. If the compiler targets ARM64EC or any non-ARM platform, this is default interpretation of volatile.

  所以如果要在msvc上测试volatile要自己手动关掉选项。

2.2 Intel的基础类型原子性

  首先明确一个问题基础类型是原子的吗?不是。首先CPP标准没有规定,其次现有的主流处理器也不都是,有一部分在特定场景下是。Intel保证对于aligned数据的访问是原子的。
  下面的内容来自于Intel® 64 and IA-32 Architectures Software Developer’s Manual Combined Volumes: 1, 2A, 2B, 2C, 2D, 3A, 3B, 3C, 3D, and 49.1.1 Guaranteed Atomic Operations。

  The Intel486 processor (and newer processors since) guarantees that the following basic memory operations will
always be carried out atomically:
• Reading or writing a byte.
• Reading or writing a word aligned on a 16-bit boundary.
• Reading or writing a doubleword aligned on a 32-bit boundary.
  The Pentium processor (and newer processors since) guarantees that the following additional memory operations
will always be carried out atomically:
• Reading or writing a quadword aligned on a 64-bit boundary.
• 16-bit accesses to uncached memory locations that fit within a 32-bit data bus.
  The P6 family processors (and newer processors since) guarantee that the following additional memory operation will always be carried out atomically:
• Unaligned 16-, 32-, and 64-bit accesses to cached memory that fit within a cache line。

  上面的文字简单总结下就是说对于部分新处理器读取特定大小的内存是原子的。为什么提这个,是因为你如果你用Intel处理器测试volatile,很大程度上会发现居然是原子的,导致错误的理解。

2.3 原子变量、Volatile和普通类型的速度

  先说结论:原子变量<volatile<普通类型,越大越快。下面是测试代码:

std::atomic<int> i = 0;
volatile int j = 0;
int g = 0;
int n = 100000000;
static void AtomicValue(benchmark::State& state) {
  // Code inside this loop is measured repeatedly
  for (auto _ : state) {
    for(volatile int k = 0;k < n;k ++){
      i++;
    }
  }
}

static void VolatileValue(benchmark::State& state) {
  // Code before the loop is not measured
  for (auto _ : state) {
    for(volatile int k = 0;k < n;k ++){
      j++;
    }
  }
}

static void IntValue(benchmark::State& state) {
  // Code before the loop is not measured
  for (auto _ : state) {
    for(volatile int k = 0;k < n;k ++){
      g++;
    }
  }
}

BENCHMARK(AtomicValue);
BENCHMARK(VolatileValue);
BENCHMARK(IntValue);

  测试结果:
C++中volatile的具体含义和可能得坑,c++,开发语言

  volatile比普通类型慢的原因是少了一些编译器的代码优化导致多次访问内存。而atomic因为要同步SB、L1、L2的数据到其他处理器缓存和内存导致变慢。对于早起的一些处理器实现atomic的方案比较粗暴,直接锁内存总线,直接导致所有CPU访存阻塞,性能更差。

  最后简单总结下,如果不是写嵌入式没有理由用volatile,用volatile来实现同步也是不合理的。文章来源地址https://www.toymoban.com/news/detail-814133.html

3 参考文献

  • Intel® 64 and IA-32 Architectures Software Developer’s Manual Combined Volumes: 1, 2A, 2B, 2C, 2D, 3A, 3B, 3C, 3D, and 4
  • cppreference-volatile
  • cpp-benchmark
  • msvc-voliate
  • llvm-volatile

到了这里,关于C++中volatile的具体含义和可能得坑的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • UG NX二次开发(C++)-Tag的含义、Tag类型与其他的转换

    在UG NX中,每个对象对应一个tag号,C++中,其类型是tag_t,一般是5位或者6位的int数字,打开模型后,tag号是唯一的。采用UFun函数时,其很多的函数都是依赖于tag_t的,不管其是输入参数还是输出参数。本文介绍一下tag号的含义,以及其与其他类型的转换,比如int型、double型、

    2024年02月15日
    浏览(52)
  • MATLAB使用hough变换函数输出[H,theta,rho]的具体含义

    输出[H,theta,rho]。 其中Theta 取值-90至89,为1×180的数组。 rho取值根据图片f的大小确定,如图片为400×400像素,则rho取值-(400^2+400^2)^0.5至(400^2+400^2)^0.5,即图片的对角线长度,为1×1131的数组。 Theta,rho仅为hough变换的坐标取值。hough变换的结果储存于输出的H矩阵中。 H矩阵为rho×

    2024年02月06日
    浏览(39)
  • uniapp开发WebRTC语音直播间支持app(android+IOS)和H5,并记录了所有踩得坑

    1. 创建自己的语音直播间 2. 查询所有直播间列表 3.加入房间 4.申请上位 5.麦克风控制 6.声音控制 7.赠送礼物(特效 + 批量移动动画) 8.退出房间 1.uniapp 实现客户端H5、安卓、苹果 2.webRTC实现语音直播间(具体原理网上有很多文章我就不讲了,贴个图) 3.使用node.js搭建信令服

    2024年02月19日
    浏览(47)
  • C++中的volatile

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

    2024年02月09日
    浏览(84)
  • C++的volatile

    在C++中,编译器为了提高代码的执行效率,常常会对变量进行反向优化,比如将变量缓存在寄存器中,这样可以减少对内存的访问次数,提高访问速度。然而,在某些情况下,我们希望确保每次访问变量时都能从内存中读取最新值,而不是使用缓存中的旧值。 volatile用

    2024年02月16日
    浏览(49)
  • volatile - (C语言)

    volatile和const一样都是一种类型修饰符,用它修饰过的变量表示可以被某些编译器未知的因素更改,比如操作系统、硬件或者是其它线程等。 该是不希望被编译器优化,从而达到稳定访问内存的目的。 示例代码:  在程序运行起来后,查看反汇编(两个版本):

    2024年02月08日
    浏览(38)
  • C语言volatile关键字

    在C语言中, volatile 是一个类型修饰符,用于告诉编译器对象的值可能会在编译器无法检测到的情况下被改变。这通常发生在以下两种情况: 硬件的输入/输出操作,例如一个设备寄存器的读取或写入。 共享内存的并行程序,其中一个线程修改了一个内存位置,而另一个线程

    2024年02月07日
    浏览(54)
  • c++ 学习 之 const,constexpr,volatile

    const、constexpr 和 volatile 是 C++ 中用于修饰变量和类型的 它们分别用于不同的用途: const(常量): const 用于声明常量,表示变量的值不能被修改。 它可以应用于变量、指针、引用、成员函数以及类对象。 常量变量必须在声明时初始化。 常量成员函数承诺不修改对象

    2024年02月09日
    浏览(47)
  • 关于c++中mutable、const、volatile这三个关键字及对应c++与汇编示例源码

    这哥三之间的关系是有趣的,不妨看看这个: cv (const and volatile) type qualifiers - cppreference.com permits modification of the class member declared mutable even if the containing object is declared const. 即便一个对象是const的,它内部的成员变量如果被mutable修饰,则此成员变量依旧可以被修改。 很常见,

    2024年02月13日
    浏览(38)
  • C++中的##、#符号含义

    在C++中, ## 和 # 是两个不同的 预处理符号 。这些符号都是在C++的预处理阶段使用的,用于在代码编译之前对 文本 进行操作。 # (字符串化操作符): 用于将 宏参数 转换为 字符串常量 。 ## (连接操作符):用于将 两个宏参数连接在一起 ,仅仅是连接在一起,它并不知道

    2024年02月09日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包