C 语言 volatile 关键字

2024年8月28日 | 阅读 7 分钟

在C语言中,volatile关键字用于指示编译器,一个变量的值可能会意外地发生变化,因此编译器不应依赖于缓存到寄存器或被优化掉的值。

当一个变量被声明为volatile时,编译器必须生成代码,每次使用该变量时都从内存中读取写入其值,而不是依赖于寄存器中的缓存值。这是因为volatile变量的值可能被外部因素改变,例如硬件或其他并发运行的线程

“volatile”使用恰当的几个场景

  • 访问内存映射的硬件寄存器:当与硬件设备(如传感器或输入/输出设备)交互时,某些内存映射寄存器的值可能会随时发生变化。在这种情况下,使用volatile至关重要,以确保编译器生成按需从寄存器读取和写入的代码。
  • 线程间共享数据:当多个线程访问相同数据时,由于不同步,存在数据损坏的风险。通过使用volatile,编译器可以生成始终从内存读取写入数据的代码,这确保了线程访问的是数据的最新值。
  • 使用非局部跳转:在使用非局部跳转(如setjmp()longjmp())时,某些变量的值可能会被缓存在寄存器中,这可能导致程序跳回到代码的先前点时出现不正确的行为。通过使用volatile,编译器可确保在需要时始终从内存读取这些变量的值。

示例

以下是使用“volatile”在C中声明变量的代码片段

注意:使用volatile可能会影响性能,因为它阻止了编译器优化对变量的访问代码。因此,建议仅在必要时使用volatile,例如在上述场景中。

以下是一些关于使用volatile的其他附加细节

  • volatile关键字可以应用于任何类型的变量,包括int、float、double,甚至structunion类型
  • 当一个变量被声明为volatile时,编译器会在每次访问变量时生成从内存读取其值的代码。它确保始终使用变量的当前值,即使外部因素已经改变了它。
  • volatile关键字通常用于嵌入式系统编程,其中硬件设备可能会随时修改内存映射寄存器的值。
  • 在多线程编程中,volatile的使用通常与其他同步技术(如信号量)结合使用,以确保多个线程能够安全一致地访问数据。
  • volatile的使用可能会对性能产生重大影响,因为它阻止了编译器执行某些优化,例如将变量值缓存在寄存器中。因此,重要的是仅在必要时才使用volatile。
  • C++中,volatile关键字的语义与C略有不同。此外,变量的值可能会意外更改,从而阻止与指令重排序相关的某些优化。此外,C++提供了单独的关键字atomic,用于指定变量在多线程环境中应以原子方式访问。

以下是C中将volatile与结构体一起使用的示例

在此示例中,MyStruct结构体xy成员被声明为volatile,表明它们的值可能会意外更改。访问这些成员时,编译器会生成每次从内存读取它们的值的代码。修改这些成员时,编译器会生成将它们的值写回内存的代码。

示例

使用volatile内存映射硬件设备通信的程序的一个示例实现

  • 在此示例中,我们将指针device_ptr声明为指向地址0x1000处的内存映射设备。我们将指针声明为volatile,以指示其指向的值可能会意外更改。
  • main()函数中,我们首先使用*device_ptr读取设备寄存器的当前值。由于device_ptr被声明为volatile,编译器会生成每次访问时从内存读取值的代码,确保我们始终获取最新值。
  • 接下来,我们通过向*device_ptr赋新值来修改设备寄存器的值。同样,由于device_ptr被声明为volatile,编译器会生成每次访问时将新值写入内存的代码。
  • 最后,我们使用*device_ptr打印出设备寄存器的新值。

注意:实际上,与硬件设备交互可能需要额外的同步和错误处理才能确保正确运行。

关于在C中使用volatile访问内存映射硬件设备的更多附加细节

  • 内存映射硬件设备通常通过指针访问,这些指针提供对设备寄存器或内存空间的直接访问。
  • 访问内存映射设备时,重要的是确保在读取或写入设备之前,设备处于已知的状态。这可能包括初始化设备、重置设备或检查错误或状态条件。
  • 由于内存映射设备可能会随时被外部因素修改(例如中断或其他共享同一总线的设备),因此在使用指向设备的指针声明时使用volatile很重要。它确保编译器生成始终读取和写入设备寄存器或内存最新值的代码。
  • 确保对内存映射设备的访问得到正确同步也很重要,尤其是在多线程环境中。这可能包括使用锁、信号量或其他同步原语来确保一次只有一个线程访问设备。
  • 使用volatile访问内存映射设备时,重要的是要确保编译器不会生成优化掉对设备读写的代码。如果编译器确定设备的值在程序的其他地方未使用,就可能发生这种情况。为防止这种情况,可以使用asm关键字插入内联汇编代码来读取或写入设备。
  • 最后,重要的是要确保内存映射设备已映射到内存中的有效物理地址。这可能涉及配置系统的内存管理单元(MMU),它将虚拟地址映射到物理地址,或使用操作系统特定的API将设备映射到内存。

示例

使用volatile访问代表计数器的内存映射设备的示例

  • 在此示例中,我们定义了内存映射设备的地址大小,并声明了一个指向内存空间的volatile指针device_ptr。之后,我们使用open()mmap()函数将设备映射到我们程序地址空间中的一个内存地址。
  • 设备映射后,我们将它的计数器值初始化为零,然后在一个循环中使用(*device_ptr)++来递增计数器。volatile关键字确保编译器生成始终读取和写入设备最新值的代码。
  • 最后,我们使用munmap()将设备从内存中取消映射,并使用close()关闭文件描述符。请注意,我们需要调用这些函数来释放内存映射设备并释放系统资源。

总而言之,内存映射设备提供了一种从软件直接访问硬件资源的强大方法。但是,重要的是使用volatile和其他技术来确保对设备的访问得到正确同步,并在访问设备之前确保设备处于已知状态。


下一主题C语言CRC程序