我在文件范围中使用了静态全局变量和静态volatile变量,
两者都由ISR和主循环更新,主循环检查变量的值。
此处在优化期间,全局变量和volatile变量都不会被优化。因此,全局变量不是使用volatile变量来解决问题。
使用全局变量而不是volatile是否合适?
使用静态volatile的任何具体原因??
任何示例程序都会很明显。
提前致谢..
答案 0 :(得分:42)
首先让我提一下静态全局变量与全局变量相同,只是您将变量限制在文件范围内。即您不能通过extern
关键字在其他文件中使用此全局变量。
因此,您可以将问题减少到全局变量和volatile变量。
现在进入volatile:
与const
类似,volatile
是一种类型修饰符。
创建volatile
关键字是为了防止可能导致代码不正确的编译器优化,特别是在存在异步事件时。
声明为volatile
的对象可能无法在某些优化中使用。
系统始终在使用它的位置读取易失性对象的当前真值,即使前一条指令要求来自同一对象的值也是如此。此外,在分配时立即写入对象的值。这意味着没有将易失性变量缓存到CPU寄存器中。
Dr. Jobb's has a great article on 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_
的值存储在寄存器中。在这种情况下,它永远不会改变。但是如果另一个线程调用wakeup,第一个线程仍在从CPU的寄存器中读取。 Wait()
永远不会醒来。
那么为什么不直接将变量缓存到寄存器中并完全避免这个问题呢?
事实证明,这种优化可以真正为您节省大量时间。因此,C / C ++允许您通过volatile
关键字明确禁用它。
上面的事实flag_
是一个成员变量,而不是一个全局变量(也不是静态全局变量)并不重要。即使您处理全局变量(和静态全局变量),示例后面的解释也会给出正确的推理。
一个常见的误解是声明变量volatile
足以确保线程安全。对变量的操作仍然不是原子的,即使它们没有在寄存器中“缓存”
volatile:
使用指针进行易失性,像const一样使用指针。
类型volatile int *
的变量意味着指针指向的变量是易失性的。
类型int * volatile
的变量意味着指针本身是易失性的。
答案 1 :(得分:21)
他们是不同的东西。我不是volatile语义的专家。但我认为这里描述的内容是有道理的。
全局只意味着有问题的标识符在文件范围内声明。有不同的范围,称为函数(其中定义了goto-labels),文件(其中包含全局变量),块(正常局部变量驻留)和函数原型(函数参数驻留)。这个概念只是用于构建标识符的可见性。它与优化没有任何关系。
static
是一个存储持续时间(我们不会在这里查看)以及一种在文件范围内部链接中声明名称的方法。这可以针对仅在一个翻译单元内所需的功能或对象来完成。典型示例可能是help
函数打印出接受的参数,并且仅从同一main
文件中定义的.c
函数调用。
6.2.2 / 2 :
如果声明范围的声明 识别对象或功能 包含存储类规范 静态的,识别者有内在的 键。
内部链接意味着标识符在当前翻译单元之外不可见(如上面的help
功能)。
挥发性是另一回事:( 6.7.3 / 6 )
具有易变质性的对象 类型可能以未知的方式进行修改 实施还是有其他的 未知的副作用。所以任何 表达这样一个对象的表达 应严格按照评估 根据抽象机的规则, 如5.1.2.3中所述。此外, 在每个序列点,值最后 存储在对象中的应同意 摘要规定的 机器,除了由修改 提到的未知因素 先前。
标准为volatile
多余( 5.1.2.3/8 )的示例提供了一个很好的示例:
实施可能会定义一个 一对一的对应关系 抽象和实际的语义:at 每个序列点,值 实际的对象会同意 摘要指定的那些 语义。关键字
volatile
那将是多余的。
序列点是完成关于抽象机的副作用的影响的点(即不包括诸如存储器单元值的外部条件)。在&&
和||
的右侧和左侧之间,;
之后,从函数调用返回的是序列点,例如。
抽象语义是编译器可以从仅查看特定程序中的代码序列中推断出来的。优化的效果在这里无关紧要。 实际语义包括通过写入对象(例如,更改存储器单元)完成的副作用的影响。将对象限定为volatile意味着总是直接从内存中获取对象的值(“由未知因素修改”)。标准没有在任何地方提到线程,如果你必须依赖于更改的顺序或操作的原子性,你应该使用平台相关的方法来确保。
为了便于理解,英特尔有一篇很棒的文章here。
继续将文件范围(全局)数据声明为volatile。全局数据本身并不意味着变量的值将等于存储在内存中的值。静态只会使你的对象在当前翻译单元本地(当前.c
文件和所有其他文件#include')。
答案 2 :(得分:14)
“volatile”关键字表示编译器不对涉及该变量的代码进行某些优化;如果您只使用全局变量,则不会阻止编译器错误地优化您的代码。
示例:
#define MYPORT 0xDEADB33F
volatile char *portptr = (char*)MYPORT;
*portptr = 'A';
*portptr = 'B';
如果没有“volatile”,第一次写入可能会被优化掉。
答案 3 :(得分:4)
volatile关键字告诉编译器确保永远不会缓存该变量。必须以一致的方式对所有访问进行访问,以便在所有线程之间具有一致的值。如果在循环检查更改时要由另一个线程更改变量的值,则希望变量是易变的,因为无法保证在某个点和循环中不会缓存常规变量值我会假设它保持不变。
答案 4 :(得分:3)
在当前环境中,它们可能不同,但微妙的变化可能会影响行为。
从长远来看,从一开始就使用适当的多线程结构会更加安全,即使现在没有它们似乎也能正常工作。
当然,如果您的程序不是多线程的,那么无关紧要。
答案 5 :(得分:3)
我+1骗子的回答。我想补充一些精度,因为在不同的答案中似乎存在很多混淆:C的volatile不是Java的不稳定。
首先,编译器可以根据程序的数据流进行大量优化,C中的volatile会阻止它,它确保每次都真正加载/存储到该位置(而不是使用擦除它的寄存器)例如)。当你有一个内存映射的IO端口时,它非常有用,正如friol所指出的那样。
C中的易失性与硬件缓存或多线程无关。它不会插入内存防护,如果两个线程访问它,您绝对不会对操作顺序保持警惕。 Java的volatile关键字就是这样做的:在需要的地方插入内存栅栏。
答案 6 :(得分:-4)
volatile变量意味着给它的值不是常量,即如果一个函数包含一个volatile变量“a = 10”并且该函数在该函数的每次调用中都加1,那么它将始终返回更新的值。
{
volatile int a=10;
a++;
}
当一次又一次调用上述函数时,变量a将不会重新初始化为10,它将始终显示更新的值,直到程序运行。
第一输出= 10
然后11
然后12
等等。