【问题标题】:What is the difference between a static global and a static volatile variable?静态全局变量和静态易失变量有什么区别?
【发布时间】:2010-09-25 16:02:48
【问题描述】:

我在文件范围内使用了一个静态全局变量和一个静态 volatile 变量,

两者都由 ISR 和主循环更新,主循环检查变量的值。

在优化期间,全局变量和 volatile 变量都没有被优化。因此,与其使用 volatile 变量,不如使用全局变量来解决问题。

那么使用全局变量而不是 volatile 好不好?

使用静态易失性的任何具体原因?

任何示例程序都会很受欢迎。

提前谢谢..

【问题讨论】:

    标签: c static global interrupt volatile


    【解决方案1】:

    它们在您当前的环境中可能没有什么不同,但细微的变化可能会影响行为。

    • 不同的硬件(更多的处理器,不同的内存架构)
    • 具有更好优化的新版本编译器。
    • 线程之间的时间随机变化。 1000 万分之一的问题可能只会出现一次。
    • 不同的编译器优化设置。

    从长远来看,从一开始就使用适当的多线程构造会更安全,即使现在没有它们似乎也能正常工作。

    当然,如果你的程序不是多线程的也没关系。

    【讨论】:

    • C 中的 'volatile' 与多线程无关。它只是阻止编译器对其进行优化(例如,寄存器映射到一些 IO 东西)。但它不会像 Java 中的 volatile 那样插入内存栅栏。
    【解决方案2】:

    “volatile”关键字建议编译器不要对涉及该变量的代码进行某些优化;如果你只使用一个全局变量,没有什么能阻止编译器错误地优化你的代码。

    例子:

    #define MYPORT 0xDEADB33F
    
    volatile char *portptr = (char*)MYPORT;
    *portptr = 'A';
    *portptr = 'B';
    

    没有“volatile”,第一次写入可能会被优化掉。

    【讨论】:

      【解决方案3】:

      volatile 关键字告诉编译器确保该变量永远不会被缓存。对它的所有访问都必须以一致的方式进行,以便在所有线程之间具有一致的值。如果在循环检查更改时变量的值将由另一个线程更改,则您希望变量是易失的,因为不能保证常规变量值不会在某些时候被缓存并且循环只会假设它保持不变。

      Volatile variable on Wikipedia

      【讨论】:

      • Cached 在这里不是正确的词,编译器无法控制 CPU 缓存如何处理事情。您正在考虑的是将值保存在 egister 中,而不是从内存中读取它。这是正确的,但我不会称之为缓存。这令人困惑。
      • 等一下,看看我对 Darron 的分析器的评论:Volatile 不适用于多线程!
      【解决方案4】:

      它们是不同的东西。我不是易变语义方面的专家。但我认为这里描述的内容是有道理的。

      全球

      全局只是意味着有问题的标识符是在文件范围内声明的。有不同的作用域,称为函数(goto-labels 定义的地方)、文件(全局变量所在的地方)、块(普通局部变量所在的地方)和函数原型(函数参数所在的地方)。这个概念只是为了构建标识符的可见性而存在。它与优化没有任何关系。

      静态

      static 是一个存储持续时间(我们不会在这里查看)和一种在文件范围内部链接中声明的名称的方法。这可以针对仅在一个翻译单元中需要的功能或对象来完成。一个典型的例子可能是一个help 函数打印出接受的参数,并且它只能从同一个.c 文件中定义的main 函数调用。

      6.2.2/2 在 C99 草案中:

      如果声明一个文件范围 对象或函数的标识符 包含存储类说明符 静态,标识符有内部 链接。

      内部链接意味着标识符在当前翻译单元之外不可见(如上面的help 函数)。

      易挥发

      易变是另一回事:(6.7.3/6)

      具有 volatile 限定的对象 类型可能会以未知的方式修改 实施或有其他 未知的副作用。因此任何 引用此类对象的表达式 严格按照 遵守抽象机器的规则, 如 5.1.2.3 所述。此外, 在每个序列点最后的值 存储在对象应同意 摘要所规定的 机器,除非经过修改 提到的未知因素 以前。

      标准提供了一个很好的例子,说明volatile 是多余的(5.1.2.3/8):

      一个实现可能定义一个 之间的一一对应 抽象和实际语义:at 每个序列点的值 实际的对象会同意 摘要指定的那些 语义。关键字volatile 那么就会是多余的。

      序列点是关于抽象机的副作用完成的点(即不包括存储单元值等外部条件)。在&& 和|| 的左右之间,在; 之后并从函数调用返回是例如序列点。

      抽象语义是编译器可以通过仅查看特定程序中的代码序列来推断的内容。优化的效果在这里无关紧要。 实际语义包括通过写入对象完成的副作用的影响(例如,改变记忆单元)。将对象限定为易失性意味着总是直接从内存中获取对象的值(“由未知因素修改”)。该标准在任何地方都没有提到线程,如果您必须依赖更改的顺序或操作的原子性,您应该使用平台相关的方式来确保这一点。

      为了易于理解的概述,英特尔有一篇很棒的文章here。

      我现在该怎么办?

      继续将您的文件范围(全局)数据声明为 volatile。全局数据本身并不意味着变量的值将等于存储在内存中的值。并且静态只会使您的对象成为当前翻译单元的本地对象(当前的.c 文件和所有其他文件#include'ed)。

      【讨论】:

        【解决方案5】:

        我 +1 friol 的回答。我想补充一些精确度,因为在不同的答案中似乎存在很多混淆:C 的 volatile 不是 Java 的 volatile。

        首先,编译器可以根据程序的数据流进行很多优化,C 中的 volatile 可以防止这种情况,它确保您每次都真正加载/存储到该位置(而不是使用擦除它的寄存器例如)。正如 friol 所指出的,当您有一个内存映射的 IO 端口时,它很有用。

        C 中的 Volatile 与硬件缓存或多线程无关。它不插入内存栅栏,如果两个线程访问它,你对操作顺序绝对没有保证。不过,Java 的 volatile 关键字正是这样做的:在需要的地方插入内存栅栏。

        【讨论】:

          【解决方案6】:

          首先让我提一下,静态全局变量与全局变量相同,只是您将变量限制在文件的范围内。 IE。您不能通过 extern 关键字在其他文件中使用此全局变量。

          因此,您可以将问题简化为全局变量与易变变量。

          现在进入 volatile:

          像const,volatile 是一个类型修饰符。

          创建volatile 关键字是为了防止编译器优化可能导致代码不正确,特别是在存在异步事件时。

          声明为volatile 的对象不能用于某些优化。

          系统总是在使用 volatile 对象时读取它的当前真实值,即使之前的指令要求来自同一对象的值也是如此。此外,对象的值在赋值时立即写入。这意味着没有将 volatile 变量缓存到 CPU 寄存器中。

          Dr. Jobb's has a great article on volatile.

          这里是乔布博士文章中的一个例子:

          class Gadget
          {
          public:
              void Wait()
              {
                  while (!flag_)
                  {
                      Sleep(1000); // sleeps for 1000 milliseconds
                  }
              }
              void Wakeup()
              {
                  flag_ = true;
              }
              ...
          private:
              bool flag_;
          };
          

          如果编译器发现Sleep() 是一个外部调用,它将假定Sleep() 不可能更改变量flag_ 的值。因此编译器可以将flag_ 的值存储在寄存器中。在那种情况下,它永远不会改变。但是如果另一个线程调用唤醒,第一个线程仍然在读取 CPU 的寄存器。 Wait() 永远不会醒来。

          那么为什么不直接将变量缓存到寄存器中并完全避免问题呢? 事实证明,这种优化确实可以总体上为您节省大量时间。因此,C/C++ 允许您通过 volatile 关键字显式禁用它。

          flag_ 是成员变量,而不是全局变量(也不是静态全局变量)这一事实无关紧要。即使您正在处理全局变量(和静态全局变量),示例之后的解释也给出了正确的推理。

          一个常见的误解是声明变量volatile 足以确保线程安全。对变量的操作仍然不是原子的,即使它们没有“缓存”在寄存器中

          带有指针的易失性:

          带有指针的易失性,就像带有指针的 const 一样。

          volatile int * 类型的变量意味着指针指向的变量是 volatile。

          int * volatile 类型的变量意味着指针本身是易失的。

          【讨论】:

          • 不错的答案。我唯一要补充的(因为这是一个常见的误解)是声明变量 volatile 不足以确保线程安全。对变量的操作仍然不是原子的,即使它们没有“缓存”在寄存器中
          【解决方案7】:

          volatile 变量意味着分配给它的值不是常数,即如果一个函数包含一个 volatile 变量“a=10”并且该函数在该函数的每次调用中加 1,那么它将始终返回更新的值。 { volatile int a=10; a++; } 当上述函数被一次又一次调用时,变量 a 不会被重新初始化为 10,它会一直显示更新后的值,直到程序运行。 第一个输出= 10 然后 11 然后 12 等等。

          【讨论】:

          • 对不起,这个答案真的是错误的。你混淆了static 和volatile。
          猜你喜欢
          • 1970-01-01
          • 2016-07-26
          • 1970-01-01
          • 2011-01-14
          • 1970-01-01
          • 2018-12-22
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多