当前位置:  开发笔记 > 前端 > 正文

为什么C需要挥发性?

如何解决《为什么C需要挥发性?》经验,为你挑选了15个好方法。

为什么volatileC需要?它是干什么用的?它会做什么?



1> Nils Pipenbr..:

Volatile告诉编译器不要优化与volatile变量有关的任何东西.

使用它只有一个原因:当您与硬件接口时.

假设您有一小块硬件映射到某处的RAM,并且有两个地址:命令端口和数据端口:

typedef struct
{
  int command;
  int data;
  int isbusy;
} MyHardwareGadget;

现在你要发送一些命令:

void SendCommand (MyHardwareGadget * gadget, int command, int data)
{
  // wait while the gadget is busy:
  while (gadget->isbusy)
  {
    // do nothing here.
  }
  // set data first:
  gadget->data    = data;
  // writing the command starts the action:
  gadget->command = command;
}

看起来很简单,但它可能会失败,因为编译器可以自由地更改写入数据和命令的顺序.这将导致我们的小工具发出具有先前数据值的命令.还要看看忙碌循环时的等待.那个将被优化.编译器会尝试聪明,只读一次isbusy的值,然后进入无限循环.那不是你想要的.

解决这个问题的方法是将指针小工具声明为volatile.这样编译器就会被迫做你写的.它不能删除内存分配,它不能在寄存器中缓存变量,也不能改变赋值顺序:

这是正确的版本:

   void SendCommand (volatile MyHardwareGadget * gadget, int command, int data)
    {
      // wait while the gadget is busy:
      while (gadget->isbusy)
      {
        // do nothing here.
      }
      // set data first:
      gadget->data    = data;
      // writing the command starts the action:
      gadget->command = command;
    }


当您使用非并发保护的数据时,线程代码中也需要易失性.是的,有效的时间可以做到这一点,你可以编写一个线程安全的循环消息队列,而不需要显式的并发保护,但它需要挥发性.
就个人而言,在与硬件交谈时,我更倾向于使用整数大小,例如int8/int16/int32.不错的答案;)
是的,你应该声明具有固定寄存器大小的东西,但是嘿 - 这只是一个例子.
更难阅读C规范.Volatile仅在内存映射设备I/O或异步中断功能触及的内存上定义了行为.它说*没有*关于线程,并且优化了多线程触及的内存访问的编译器是符合的.
@tolomea:完全错了.伤心的17个人不知道.volatile不是记忆围栏.它只与基于非可见副作用假设的优化期间的__avoiding代码省略有关.
你有这个说法的参考吗?我见过的每个引用都表示volatile会告诉编译器不要优化内存访问.我从来没有看到内存是RAM还是IO的限制.此外,在许多平台上,编译器无法区分RAM和内存映射IO.
当你与硬件接口时,我会说"当变量可以被程序之外的东西改变时",而不是说"volatile"被用来".这包括内存映射硬件案例以及共享内存等.
答案在很多方面都是错误的,例如寄存器中确实存在`volatile`变量.特别是`volatile`确实对内存映射寄存器很有用.疯狂这个答案有67个upvotes ...下一个错误的是重新订购保证,请阅读http://software.intel.com/en-us/blogs/2007/11/30/volatile-almost-useless-对于多线程编程
@DavidThornley,@ tolomea:虽然易失性存储器访问被定义为必须按书面执行并且没有重新排序的可观察行为,但是没有定义它也是原子地执行的.它根本不需要.这就是为什么C++ 11引入了`std :: atomic`类型,它具有更强的保证.有关`volatile`和线程的讨论,请参见[N2016](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2016.html)(来自C++ 11标准化过程的白皮书) ).
如果使用volatile来实现并发而不是原子变量或互斥,那么你最好知道自己在做什么.
@JanHudec:澄清一下,他们只需维持其他`volatile`变量之间的顺序.它们不会成为记忆障碍.
在信号处理程序中,真正安全的少数事情之一是设置"volatile sigatomic_t"类型的全局变量.此外,在使用setjmp()的地方,您应该担心哪些局部变量被标记为volatile.
为了更好地解释Jonathan Leffler所说的,调用setjmp()的函数本地的变量在setjmp()和longjmp()调用之间已经改变,除非声明为volatile,否则它们具有未定义的值.

2> Manoj Doubts..:

volatile在C中实际上是为了不自动缓存变量的值而存在的.它会告诉机器不要缓存此变量的值.因此,volatile每当遇到它时,它将从主存储器中获取给定变量的值.使用此机制是因为OS可以在任何时候修改该值或任何中断.所以使用volatile将帮助我们每次重新获取价值.


@FaceBro:`volatile`的目的是让编译器能够优化代码,同时仍允许程序员实现在没有这种优化的情况下实现的语义.标准的作者期望质量实现将支持任何语义在给定目标平台和应用程序字段时有用,并且不期望编译器编写者会寻求提供符合标准的最低质量语义而不是100%愚蠢(请注意,标准的作者在理由中明确承认......

3> CesarB..:

volatile信号处理程序的另一个用途.如果您有这样的代码:

int quit = 0;
while (!quit)
{
    /* very small loop which is completely visible to the compiler */
}

允许编译器注意到循环体不接触quit变量并将循环转换为while (true)循环.即使quit变量是在信号处理程序上设置的SIGINTSIGTERM; 编译器无法知道这一点.

但是,如果quit声明了变量volatile,则每次都强制编译器加载它,因为它可以在别处修改.在这种情况下,这正是您想要的.


@PierreG.不,编译器总是可以假设代码是单线程的,除非另有说明.也就是说,在没有`volatile`或其他标记的情况下,它会假设循环外的任何东西一旦进入循环就会修改该变量,即使它是一个全局变量.

4> Chris Jester..:

volatile告诉编译器您的变量可能通过其他方式更改,而不是访问它的代码.例如,它可以是I/O映射的存储器位置.如果在这种情况下没有指定,则可以优化一些变量访问,例如,其内容可以保存在寄存器中,并且存储器位置不会再次读回.



5> Robert S. Ba..:

请参阅Andrei Alexandrescu撰写的这篇文章," volatile - 多线程程序员最好的朋友 "

挥发性关键字进行设计,以防止可能使代码在某些异步事件的存在不正确的编译器优化.例如,如果声明一个原始变量作为 挥发性,编译器不允许它缓存在一寄存器-共同优化如果该变量被多个线程之间共享,这将是灾难性的.所以一般规则是,如果你有必须在多个线程之间共享的基本类型的变量,则声明那些变量volatile.但是你可以用这个关键字做更多的事情:你可以用它来捕获非线程安全的代码,你可以在编译时这样做.这篇文章展示了它是如何完成的; 该解决方案涉及一个简单的智能指针,也可以轻松地序列化关键代码段.

本文适用于CC++.

另请参阅Scott Meyers和Andrei Alexandrescu 撰写的文章" C++和双重锁定的危险 ":

因此,与一些存储器位置打交道时(由ISR引用例如存储器映射的端口或存储器[中断服务例程]),某些优化必须暂停.挥发性存在用于指定特殊处理用于这样的位置,特别是:(1)挥发性变量的内容是"不稳定"(可以改变由装置未知的编译器),(2)所有写入到易失性数据是"可观察",所以他们必须以宗教的方式执行,以及(3)对易失性数据的所有操作都按照它们在源代码中出现的顺序执行.前两条规则确保正确的阅读和写作.最后一个允许实现混合输入和输出的I/O协议.这是非正式的C和C++的易变性保证.



6> 小智..:

我的简单解释是:

在某些情况下,基于逻辑或代码,编译器将对其认为不会更改的变量进行优化.所述volatile关键字防止被优化的变量.

例如:

bool usb_interface_flag = 0;
while(usb_interface_flag == 0)
{
    // execute logic for the scenario where the USB isn't connected 
}

从上面的代码中,编译器可能认为usb_interface_flag定义为0,而在while循环中它将永远为零.优化后,编译器会一直对待它while(true),导致无限循环.

为了避免这种情况,我们将标志声明为volatile,我们告诉编译器这个值可能被外部接口或程序的其他模块改变,即请不要优化它.那是volatile的用例.



7> Alexandre C...:

volatile的边际用途如下.假设您要计算函数的数值导数f:

double der_f(double x)
{
    static const double h = 1e-3;
    return (f(x + h) - f(x)) / h;
}

问题是由于舍入误差x+h-x通常不等于h.想一想:当你减去非常接近的数字时,会丢失很多有效数字,这可能会破坏导数的计算(想想1.00001-1).可能的解决方法是

double der_f2(double x)
{
    static const double h = 1e-3;
    double hh = x + h - x;
    return (f(x + hh) - f(x)) / hh;
}

但是根据您的平台和编译器开关,该功能的第二行可能会被积极优化的编译器消灭.所以你写了

    volatile double hh = x + h;
    hh -= x;

强制编译器读取包含hh的内存位置,从而丧失最终的优化机会.


是不是更可读的方式来写这个`x1 = x + h; d =(f(x1)-f(x))/(x1-x)`?不使用挥发性物质.
“ h”和“ hh”之间的区别在于,“ hh”被操作“ x + h-x”截断为负的2的幂。在这种情况下,`x + hh`和`x`的区别完全在于`hh`。您也可以采用公式,因为“ x + h”和“ x + hh”相等(因此分母在这里很重要),所以得出的结果相同。

8> 小智..:

有两种用途.这些在嵌入式开发中经常被特别使用.

    编译器不会优化使用volatile关键字定义的变量的函数

    易失性用于访问RAM,ROM等中的确切存储器位置......这种情况更常用于控制存储器映射设备,访问CPU寄存器和定位特定存储器位置.

查看汇编列表的示例. Re:在嵌入式开发中使用C"volatile"关键字



9> Diomidis Spi..:

当您想强制编译器不优化特定代码序列时(例如,用于编写微基准测试),易失性也很有用.



10> Alexey Frunz..:

我会提到另一种情况,即挥发物很重要.

假设您对文件进行内存映射以获得更快的I/O,并且该文件可以在后台更改(例如,该文件不在本地硬盘驱动器上,而是由另一台计算机通过网络提供).

如果通过指向非易失性对象的指针(源代码级别)访问内存映射文件的数据,那么编译器生成的代码可以多次获取相同的数据,而不会发现它.

如果该数据发生变化,您的程序可能会使用两个或更多不同版本的数据并进入不一致状态.这不仅会导致程序的逻辑错误行为,而且如果它处理来自不受信任位置的不受信任文件或文件,也会导致其中存在可利用的安全漏洞.

如果你关心安全性,那么这是一个需要考虑的重要方案.



11> 小智..:

volatile意味着存储可能随时更改并被更改,但不受用户程序控制之外的限制.这意味着如果引用变量,程序应始终检查物理地址(即映射的输入fifo),而不是以缓存方式使用它.



12> coanor..:

维基说出一切volatile:

易变(计算机编程)

Linux内核的doc也是一个很好的表示法volatile:

为什么不应该使用"volatile"类型类



13> 小智..:

我认为,您不应期望太多volatile。为了说明这一点,请看Nils Pipenbrinck极受好评的答案中的示例。

我想说,他的榜样不适合volatilevolatile仅用于: 防止编译器进行有用且理想的优化。与线程安全,原子访问甚至内存顺序无关。

在该示例中:

    void SendCommand (volatile MyHardwareGadget * gadget, int command, int data)
    {
      // wait while the gadget is busy:
      while (gadget->isbusy)
      {
        // do nothing here.
      }
      // set data first:
      gadget->data    = data;
      // writing the command starts the action:
      gadget->command = command;
    }

gadget->data = data之前gadget->command = command仅只有在编译器编译代码的保证。在运行时,关于处理器体系结构,处理器仍可能对数据和命令分配进行重新排序。硬件可能会获得错误的数据(假设小工具已映射到硬件I / O)。在数据和命令分配之间需要内存屏障。


我想说说volatile是用来防止编译器进行通常情况下有用且理想的优化。如所写,听起来“ volatile”无缘无故地降低了性能。至于是否足够,将取决于程序员可能比编译器了解更多的系统其他方面。另一方面,如果处理器保证写入特定地址的指令将刷新CPU高速缓存,但是编译器没有提供刷新CPU所不知道的寄存器高速缓存变量的方法,则刷新高速缓存将是无用的。

14> supercat..:

在丹尼斯·里奇(Dennis Ritchie)设计的语言中,对任何对象的每次访问(未使用其地址的自动对象除外)都将表现为好像计算了对象的地址,然后在该地址读取或写入了存储。这使得该语言非常强大,但是优化机会却非常有限。

虽然可能可以添加一个限定符来邀请编译器假定一个特定的对象不会以怪异的方式进行更改,但是这种假定将适用于C程序中的绝大多数对象。在这样的假设适用的所有对象上添加限定符是不切实际的。另一方面,某些程序需要使用某些对象,而这些对象对此假设将不成立。为解决此问题,标准指出,编译器可能会假设未声明的对象volatile将不会以其编译器无法控制的方式或合理的编译器无法理解的方式来观察或更改其值。

由于各种平台可能具有不同的方式,可以在编译器的控制范围之外观察或修改对象,因此适合这些平台的高质量编译器应在volatile语义的精确处理方面有所不同。不幸的是,由于该标准未能建议打算在平台上进行低级编程的高质量编译器应volatile以能够识别该平台上特定读/写操作的所有及所有相关影响的方式进行处理,因此许多编译器无法做到这一点因此,这种方式使得以高效的方式处理诸如后台I / O之类的事情变得更加困难,但不会被编译器的“优化”破坏。



15> 小智..:

简单来说,它告诉编译器不要对特定变量进行任何优化。映射到设备寄存器的变量由设备间接修改。在这种情况下,必须使用挥发物。

推荐阅读
Life一切安好
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有