28

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

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

在优化过程中,全局变量和 volatile 变量都没有被优化。因此,不是使用 volatile 变量,而是使用全局变量来解决问题。

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

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

任何示例程序都将是可观的。

提前致谢..

4

7 回答 7

44

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

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

现在进入易失性:

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

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

声明为的对象volatile可能不会在某些优化中使用。

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

Dr. Jobb's 有一篇关于 volatile 的好文章

以下是 Jobb 博士文章中的一个示例:

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 *变量意味着指针指向的变量是易失的。

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

于 2008-12-06T15:51:55.073 回答
21

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

全球的

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

静止的

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

C99 草案中的6.2.2/2 :

如果对象或函数的文件范围标识符的声明包含存储类说明符 static,则该标识符具有内部链接。

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

易挥发的

易失性是另一回事:(6.7.3/6

具有 volatile 限定类型的对象可能会以实现未知的方式进行修改或具有其他未知的副作用。因此,任何引用此类对象的表达式都应严格按照抽象机的规则进行评估,如 5.1.2.3 中所述。此外,在每个序列点,最后存储在对象中的值应与抽象机规定的值一致,除非由前面提到的未知因素修改。

该标准提供了一个很好的例子来说明一个volatile冗余的例子(5.1.2.3/8):

一个实现可能会定义抽象语义和实际语义之间的一一对应关系:在每个序列点,实际对象的值将与抽象语义指定的值一致。那么关键字volatile 将是多余的。

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

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

对于易于理解的概述,英特尔在这里有一篇很棒的文章。

我现在该怎么办?

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

于 2008-12-06T15:35:04.030 回答
14

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

例子:

#define MYPORT 0xDEADB33F

volatile char *portptr = (char*)MYPORT;
*portptr = 'A';
*portptr = 'B';

没有“易失性”,第一次写入可能会被优化。

于 2008-12-06T14:21:18.047 回答
4

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

维基百科上的易变变量

于 2008-12-06T14:21:47.593 回答
3

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

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

从长远来看,从一开始就使用适当的多线程结构要安全得多,即使现在没有它们似乎也可以工作。

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

于 2008-12-06T14:20:13.573 回答
3

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

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

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

于 2008-12-06T15:35:56.510 回答
-4

volatile variable means that the value assinged to it is not constant, i.e if a function containing a volatile variable "a=10" and the function is adding 1 in each call of that function then it will always return updated value. { volatile int a=10; a++; } when the above function is called again and again then the variable a will not be re-initialised to 10, it will always show the updated value till the program runs. 1st output= 10 then 11 then 12 and so on.

于 2011-07-17T15:40:29.330 回答