为什么我们要使用volatile关键字?
为什么我们要使用volatile关键字?
可能是重复问题:
\n为什么要使用volatile关键字?\n我从来没有使用过它,但我想知道为什么人们要使用它?它到底有什么作用?我在论坛上搜索了一下,发现只有关于C#或Java的主题。
为什么我们使用volatile关键字?
在计算机编程中,特别是在C、C++和C#编程语言中,使用volatile关键字声明的变量或对象通常具有与优化和/或线程相关的特殊属性。一般来说,volatile关键字旨在防止编译器对假设变量值“自行更改”的代码应用任何优化。volatile关键字是一种类型限定符,用于声明一个对象可以被程序中的某些内容(如操作系统、硬件或并发执行的线程)修改。(c) MSDN C++ Reference.
volatile关键字与线程无关。
volatile关键字是一种类型限定符,用于声明一个对象可以被程序中的某些内容(如操作系统、硬件或并发执行的线程)修改。(c) MSDN C++ Reference。但是,msdn和维基百科肯定是错误的,你是正确的。
volatile关键字对线程没有帮助。volatile读取/写入仍然可以与非volatile读取/写入进行重排序,这使得它对线程目的毫无用处。此外,您可能已经注意到MSDN页面上有一个大大的“Microsoft Specific”。Microsoft对volatile的实现提供了超出标准规定的额外保证。所以,从技术上讲,MSDN是错误的。维基百科也有错误并不令人意外。
尽管如此,volatile的特性与线程有关。在多个线程之间共享的变量上使用volatile可以改变程序的语义。然而,volatile并不足以将语义更改为任何有用的或良好定义的内容。
我担心你不知道你在说什么。volatile关键字确保在一个线程中对变量的更改在所有线程中保持一致。通过根据需要插入屏障、同步点、缓存刷新、重新加载等平台相关的操作来实现。如果你不只是开发个人电脑,你可能会知道这一点。仅仅因为英特尔对缓存管理隐藏了很多东西,并不意味着它不会发生。
在Java中,volatile与线程有关。我相信在C++和C#中也有类似的影响。
但它的确有用...例如,当你关心内存是否是最新的,但并不完全准确时:一个缓存刷新机制读取由其他线程更新的“last_update”变量。你不在乎缓存稍后刷新几毫秒,但你需要“接近真实值”(请记住,在某些语言,如Java中,如果没有volatile,一个线程看到另一个线程所做的更改可能需要几秒钟或几分钟)。
volatile的可能用途很少,而且不太强大,但在这些受限制的情况下,volatile是可以的。原子变量更好,但volatile也可以。
“实际上,你不知道你在说什么。”实际上,你才是那个不知道你在说什么的人;哪个编译器在哪个系统上的哪种语言为哪个操作插入了缓存刷新呢?
我并不是指涉及缓存刷新,而是指插入围栏或内存管理技术。在Java中,内存模型确保所有线程看到变量的一致值,防止其他线程读取旧值(可能被缓存)。在C++中,这取决于平台,但某些架构显然会引入围栏,你是对的,似乎与多线程无关。在C#中,volatile与多进程相关。
确实在Java中,volatile是线程之间的通信工具,对这些标量的写入具有特定的含义。在C和C++中,volatile主要是用于MMIO(扩展卡、驱动程序等)、异步信号、特殊伪函数setjmp,还有其他与线程无关的用途。C和C++中的标准支持线程间通信是通过原子对象(在C++中拼写为std::atomic
通过哪些指令来实现这些功能?
这是一个关于C++的问题,我的评论是关于Java和C#的(一旦被证明我对C++的理解是错误的),所以我们不应该在这个线程上混淆。无论如何,对于Java,请阅读这个和这个,在这里有一个经典示例:stackoverflow.com/questions/11595868/...。对于C#,使用Google。
为什么我们要使用volatile关键字?
考虑以下代码,
int some_int = 100; while(some_int == 100) { //你的代码 }
当编译这个程序时,如果编译器发现程序从来没有试图改变some_int
的值,那么它可能会对这段代码进行优化,将while
循环从while(some_int == 100)
改为等价于while(true)
的形式,以便执行速度更快(因为while
循环的条件看起来始终为true
)。如果编译器不进行优化,那么它就需要在每次迭代中获取some_int
的值并与100进行比较,这显然会比较慢。
然而,有时候优化(程序的某些部分)可能是不可取的,因为可能有其他人从程序之外改变了some_int
的值,而编译器并不知道,因为它看不到;但这是你设计的方式。在这种情况下,编译器的优化将无法产生期望的结果!
因此,为了确保期望的结果,你需要通过某种方式阻止编译器优化while
循环。这就是volatile
关键字发挥作用的地方。你只需要这样做,
volatile int some_int = 100; //注意现在有了'volatile'修饰符!
插入引用块:
volatile
告诉编译器,
"嘿,编译器,我是易变的,你知道,我可以被一些你甚至不知道的XYZ改变。那个XYZ可以是任何东西。也许是这个星球之外叫做程序的外星人。也许是闪电,某种形式的中断,火山等等可以改变我的。也许。你永远不知道谁会改变我!所以,噢,你这个无知的人,不要扮演一个无所不知的上帝,不要冒然触碰我所在的代码。好吗?"
这就是volatile
如何阻止编译器优化代码的方式。现在搜索网络以查看一些示例代码。
引用C++标准($7.1.5.1/8)中的内容:
[..]volatile是一个提示给实现的,要求它避免使用与对象相关的激进优化,因为对象的值可能通过一些实现无法检测到的方式进行更改。[...]
相关主题:
使结构体变量volatile是否会使其所有成员变量都变为volatile?
需要强调的是,volatile
是一个限定符,类似于const
(但意义不同),因此你也可以声明只能在volatile
实例上调用的volatile
方法。
我认为这就是extern
关键字的作用吗?
:不。 extern
关键字用于其他用途。
根据你的例子,编译器在这里对代码进行了很好的优化,因为我猜测在while函数内部它没有改变some_int的值。你能否也解释一下可能发生some_int值更改的场景?如果没有,那么在这个例子中使用volatile有什么用?
很好的答案,可以提到在示例中,some_int的值可以被另一个线程或中断例程改变,这是代码将失败的两个示例,只是为了澄清。(-这应该回答了你的评论)
因此,由于值可能从程序之外被更改,缓存变量的内容也会被volatile
使用禁用吗?
不明白,这是否意味着如果程序员忘记在应该使用"volatile"的地方使用它,编译器可能会生成错误的二进制可执行文件?有人能提供一个验证这个概念的示例代码吗?谢谢。
:我不认为有人有时间来编写一个验证这个概念的示例代码,因为那是一项困难的任务。请注意,没有volatile
并不保证变量的加载会被优化掉。只是volatile
的存在确保它不会被优化掉,即使在没有关键字的情况下也很可能发生这种情况;不同之处在于volatile
的不存在不保证这一点。
一个程序可以访问另一个程序的内存吗?
是的,一个程序可以访问另一个程序的内存。一个例子是操作系统可以访问任何它想要的内存,无论它是否属于它。另一个例子是两个访问由mmap创建的共享缓冲区的程序,例如。
我在过去的某个地方读到volatile
是为了确保一个变量占据一个内存空间,并且不会被编译器优化掉。对我来说,这个意义对volatile
的很多可能用途都有意义。我猜,volatile
现在在内部更高级,不总是需要实际的内存空间来存储变量。是这样吗?请帮我澄清一下。
:如果它不需要实际的内存空间,那么值将存储在哪里?以这样的方式思考:volatile int val = 10;
,那么&val
指向一个有效的地址吗?
,所以可以说,即使在当前的内部实现中,volatile
始终为变量提供内存空间吗?我读过stackoverflow上关于volatile
影响的几个答案和评论,让我好奇和困惑的是,他们只说"编译器不会优化掉",而不是自信地说"它确保为变量提供内存空间"。所以我想可能现在有一些更多/替代的东西在volatile
内部,除了给它提供"内存空间",仍然可以实现"易变性"。
,是的,这就是我从我在这里的第一条评论中担心的。多年来,我相信volatile
是为了确保变量在内存中,而这对我很有帮助。但是最近,包括在这个stackoverflow页面上,我没有读到任何一个自信地说volatile
变量将占据内存空间,而是像:“编译器不会优化掉”,让我想:“也许现在有一些其他的东西了”。我希望你能理解我的观点,我觉得我们在“不同的页面”。但是尽管我读过的所有东西,我想我还是会选择我所知道的volatile
的旧含义。
提供volatile
所需的语义可能需要确保变量在某个特定平台上具有内存空间。对于已经存在或将来可能存在的所有平台,无法知道这是否是必需的。哪怕可以想象一个只有CPU寄存器的平台,只有很多很多寄存器。
volatile
用于修复在没有一致内存的系统上出现的许多问题,比如PS3、XboxOne等。要完全理解volatile
关键字,必须了解一致内存和非一致内存的区别。
通过ptrace
或类似的方式,在支持程序调试的任何操作系统上访问另一个程序的内存。volatile
保证在停止执行到一个序列点时(也可以使用ptrace
停止线程),可以检查变量的值(它按照ABI表示)甚至进行修改。
另一个线程也可以是"你甚至不知道的一些XYZ"吗?
:是的。可能是!
你是否可以分享一个以下情况的示例:“...来自编译器不知道的程序外部更改了some_int的值,因为它看不到,但这是你设计的方式...”?
哦,你应该写一本令人愉快的C++圣经
这个答案是错误的。你给出的例子中行为的改变是由翻译器进行的语法树优化引起的(不是由标准指定的)。此外,标准明确指出这种行为不被保证(标记为volatile的变量将在存储后进行加载)。你引用的标准部分与抽象执行引擎有关,它有一个非常明确的一组优化,编译器可以执行(gcc/clang不遵守volatile关键字的限制,并且大多数情况下将其委托给-O标志)。
:"标记为volatile的变量将在存储后进行加载"。哪部分规范说了这个?
我的意思是,标记为volatile的变量将在存储后进行加载,而不是在寄存器中重用值。至于标准,在C11第6.7.3节中,参见第7款。最初它声明:“此外,在每个序列点上,对象中最后存储的值应符合抽象机器所规定的值,除非被之前提到的未知因素修改”-大多数编译器将其解释为每次都进行加载。紧接着它又声明对volatile的访问是实现定义的(所以你的解释可能不同)。
:我不明白你对这个答案的反驳是什么。我也发现很难将你的说法与你从规范中引用的文本联系起来。你能否详细解释一下?或者发表一个答案?或者在github上发布一个gist并在这里发布链接?
当阻止编译器执行某些优化时,如果这些优化恰好可以由CPU、内存控制器、操作系统、系统库、驱动程序或系统的任何其他组件执行,那么这有什么帮助呢?由于volatile
不能保证阻止编译器进行优化的任何特定效果,除非在提供实际保证的特定平台上使用,否则它有什么用处?