【问题标题】:Where all to use volatile keyword in C在 C 中哪里都可以使用 volatile 关键字
【发布时间】:2017-05-29 13:04:19
【问题描述】:

我知道 volatile 关键字会阻止编译器优化变量并在读取时从内存中读取它。除了内存映射寄存器,还有哪些情况需要我们使用 volatile?给定一个符合标准的编译器,我是否必须在两种情况下都将 test_var 声明为 volatile?

1.

在file1.c中

int test_var=100;


void func1()
{
    test_var++;
}

在file2.c中

extern int test_var;

void func2()
{
    if(test_var==100)
    {
      ....
    }
}

2.

在file1.c中

int test_var=100;

void func1()
{

}

在file2.c中

extern int test_var;

void func2()
{
    if(test_var==100)
    {
      ....
    }
}

【问题讨论】:

  • 这可能在多线程应用程序中也是必需的。
  • 请注意,将您的变量声明为 extern 存储类,编译器在优化对该变量的读取和写入的能力方面受到限制。
  • @Jean-FrançoisFabre 这将是一个经典错误,请参阅我的答案。
  • @FelixPalmen 很好的答案顺便说一句

标签: c embedded driver volatile


【解决方案1】:

内存映射 I/O 是 C 中volatile唯一通用用法。*)

对于 POSIX 信号,volatile 也可以与类型 sig_atomic_t 一起使用,如下所示:

volatile sig_atomic_t signal_occured = 0;

您的任何场景都不应该需要volatile。如果您感兴趣的只是保证在不同编译单元之间更新值,请参阅 tofro 的评论,extern 已经保证了这一点。特别是,volatile 不是 C 中线程同步的正确工具。它只会引入错误,因为正如您所说,它确实需要实际读写访问变量,但它确实强制它们相对于线程的正确顺序(它缺少内存屏障,谷歌了解详细信息)。

请注意,这与 volatile 设计为在线程之间工作的其他一些语言不同。

在嵌入式系统中,volatile 可能足以在 ISR(中断服务例程)和主程序之间进行通信,当结合原子读/写的数据类型时,就像sig_atomic_t 用于 POSIX 信号.请查阅编译器的文档。


*) C 标准仅在脚注中提到了这一点,以及“异步中断函数”的用例,因为内存映射 I/O 超出了该语言的范围。该语言只是定义了 volatile 的语义,使其适用于内存映射 I/O。

【讨论】:

  • 因为您似乎在区分标准 C 和 POSIX(或其他)C 扩展,所以您的开场白有点奇怪。据我所知,标准 C 不提供内存映射 I/O,所以您不应该说“在 C 中没有通用使用 volatile”吗?
  • @JohnBollinger 很好,第 6.7.3 节中的脚注 134 解释了volatile 的这种预期用途(“易失性声明可用于描述与内存映射输入对应的对象/输出端口”),但它也提到了“异步中断函数”(POSIX信号的用例),所以你可能是对的;)
  • @JohnBollinger 作为volatile 的“理由”很可能是内存映射 I/O,我将留下它并添加一个脚注。
  • 这个答案具有误导性。在 C 标准中有明确使用volatile,即用于信号处理程序和longjmp,请参阅我的回答。
【解决方案2】:

在您的两个示例中,volatile 都不是必需的。

volatile 是必需的:

  1. 变量可能在单个执行线程控制之外发生更改,
  2. 需要进行变量访问的任何地方,即使它在语义上没有影响。

案例一包括:

  • 内存映射 I/O 寄存器,
  • 用于 DMA 传输的内存,
  • 在中断和/或线程上下文之间共享内存,
  • 独立处理器之间共享的内存(例如双端口 RAM)

案例 2 包括:

  • 用于空延迟循环的循环计数器,否则整个循环可能会被完全优化掉并且不会花费时间,
  • 在调试器中写入但从未读取以供观察的变量。

上面的例子可能并不详尽,但关键是volatile 的语义;该语言只需执行源代码所指示的显式访问。

【讨论】:

  • “在中断和/或线程上下文之间共享内存”和“在独立处理器之间共享内存(例如双端口 RAM)”的示例在 C 中是危险的。简而言之:有一些东西是必要的,但通常不仅仅是volatilevolatile 通常对这些情况无用。
  • @FelixPalmen :我不确定“无用”是否完全正确-肯定不是完整的解决方案。假设问题已通过 volatile 单独解决 is 危险;但它仍然是volatile 发挥作用的情况。
  • @FelixPalmen 危险并不总是意味着无用。
  • @Clifford 一旦你使用了确保线程之间正确同步的构造,volatile 是多余的。如果您的环境/编译器为您提供了一些保证,嵌入式平台上的 ISR 可能是一个例外,但除此之外 - 如果您在多个线程访问相同数据的行中发现 volatile,您就会发现错误。
  • 可以在embedded.com/design/programming-languages-and-tools/4442490/1/… 找到关于 SO 的更广泛的文章,在 embedded.com/electronics-blogs/beginner-s-corner/4023801/… 可以找到嵌入式系统中 volatile 的基础知识
【解决方案3】:

除了诸如内存映射设备之类的扩展之外,在标准 C 中volatile 有两个用例:与信号处理程序交互以及在使用 setjmp/longjmp 时修改对象。两种情况都存在优化器可能没有意识到的异常控制流。

【讨论】:

  • 内存映射设备不是“扩展”,它们只是可能出现在“外部”的东西,是环境的一个属性,它们在标准中被提及
【解决方案4】:

在使用中断的 C 微控制器应用程序中,volatile 关键字必不可少,以确保在中断中设置的值正确保存在中断中,然后在主处理中具有正确的值环形。未能使用volatile 可能是基于定时器的中断或基于 ADC(模数转换)的中断在中断后返回处理器状态后恢复控制流时具有损坏值的最大原因。来自 Atmel 和 GCC 的规范模板:

volatile uint8_t flag = 0;

ISR(TIMER_whatever_interrupt)
{
    flag = 1;
}

while(1) // main loop
{
    if (flag == 1)
    {
        <do something>
        flag = 0;
    }
}

如果没有volatile,它保证不会按预期工作。

【讨论】:

    【解决方案5】:

    除了内存映射寄存器,还有哪些情况 我们需要使用 volatile 吗?

    如果

    1. 执行是纯顺序的(没有线程,也没有异步传递的信号);
    2. 你不要使用longjmp
    3. 您不需要能够调试经过优化编译的程序
    4. 您不要使用具有模糊指定语义的构造,例如 浮点运算
    5. 不要像在基准循环中那样进行无用计算(忽略结果的计算);
    6. 您不会对任何纯计算进行计时,即任何不基于 I/O(基于 I/O,例如访问网络请求、外部数据库访问的计时)的计算

    那么你可能不需要 volatile。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-24
      • 2011-02-08
      • 2015-10-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-22
      • 1970-01-01
      相关资源
      最近更新 更多