fbpx
维基百科

Volatile变量

程序设计中,尤其是在C语言C++C#Java语言中,使用volatile关键字声明的变量对象通常具有与优化、多线程相关的特殊属性。通常,volatile关键字是用来阻止(伪)编译器因誤認某段程式碼無法被程式碼本身所改變,而造成的過度優化。如在C语言中,volatile关键字可以用来提醒编译器它后面所定义的变量随时有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。

在C环境中,volatile关键字的真实定义和适用范围经常被误解。虽然C++、C#和Java都保留了C中的volatile关键字,但在这些编程语言中volatile的用法和语义却大相径庭。

C和C++中的volatile

在C,以及C++中,volatile关键字的作用[1]

  • 允许访问内存映射设备
  • 允许在setjmplongjmp之间使用变量
  • 允许在信号处理函数中使用sig_atomic_t变量

根据相关的标准(C,C++,POSIX,WIN32)和目前绝大多数实现,对volatile变量的操作并不是原子的,也不能用来为线程建立严格的happens-before关系。volatile关键字就像便携式线程构建一样基本没什么用处[1][2][3][4][5]

Visual C++ 2005 保证volatile变量是一种内存屏障,阻止编译器和CPU重新安排读入和写出语义。[6] 在先前版本的Visual C++则没有此类保证。在其他方面将指针定义为volatile可能会影响程序的性能。例如,如果指针定义对代码的其他地方可见,强制编译器将指针视为屏障,就会降低程序的性能,这是完全不必要的。

对用户定义的非基本数据类型使用volatile

基本类型的对象用volatile修饰后,仍旧支持所有的操作(加、乘、赋值等)。但是,用户定义的非基本类型(class、struct、union)的对象被volatile修饰后,具有不同行为:

  • 只能调用volatile成员函数;即只能访问它的接口的子集。
  • 只能通过const_cast运算符转为没有volatile修饰的普通对象。即由此可以获得对类型接口的完全访问。
  • volatile性质会传递给它的数据成员。

volatile与多线程语义

临界区内部,通过互斥锁(mutex)保证只有一个线程可以访问,因此临界区内的变量不需要是volatile的;而在临界区外部,被多个线程访问的变量应为volatile,这也符合了volatile的原意:防止编译器缓存(cache)了被多个线程并发用到的变量。volatile对象只能调用volatile成员函数,这意味着应仅对多线程并发安全的成员函数加volatile修饰,这种volatile成员函数可自由用于多线程并发或者重入而不必使用临界区;非volatile的成员函数意味着单线程环境,只应在临界区内调用。在多线程编程中可以令该数据对象的所有成员函数均为普通的非volatile修饰,从而保证了仅在进入临界区(即获得了互斥锁)后把该对象显式转为普通对象之后才能调用该数据对象的成员函数。这种用法避免了编程者的失误——在临界区以外访问共享对象的内容:

template <typename T> class LockingPtr{  public:  LockingPtr(volatile T& obj, Mutex& mtx)  :pObj_(const_cast<T*>(&obj) ), pMtx_(&mtx)  { mtx.Lock(); }  ~LockingPtr()  { pMtx->Unlock(); }  T& operator*()   { return *pObj_; }  T* operator->()  { return pObj_; }  private:  T* pObj_;  Mutex* pMtx_;  LockingPtr(const LockingPtr&);  LockingPtr& operator=(const LockingPtr&); } 

对于内建类型,不应直接用volatile,而应把它包装为结构的成员,就可以保护了volatile的结构对象不被不受控制地访问。

C语言中MMIO的例子

在这里例子中,代码将foo的值设置为0。然后开始不断地轮询它的值直到它变成255

static int foo;   void bar(void) {  foo = 0;    while (foo != 255)  ; } 

一个执行优化的编译器会提示没有代码能修改foo的值,并假设它永远都只会是0.因此编译器将用类似下列的无限循环替换函数体:

void bar_optimized(void) {  foo = 0;    while (true)  ; } 

但是,foo可能指向一个随时都能被计算机系统其他部分修改的地址,例如一个连接到中央处理器的设备的硬件寄存器,上面的代码永远检测不到这样的修改。如果不使用volatile关键字,编译器将假设当前程序是系统中唯一能改变这个值部分(这是到目前为止最广泛的一种情况)。 为了阻止编译器像上面那样优化代码,需要使用volatile关键字:

static volatile int foo;   void bar (void) {  foo = 0;    while (foo != 255)  ; } 

这样修改以后循环条件就不会被优化掉,当值改变的时候系统将会检测到。

C语言中的优化对比

下面的C程序和后面的汇编代码展示了volatile关键字如何影响编译器的输出。这里使用的编译器是GCC。

Java中的volatile

Java也支持 volatile 关键字,但它被用于其他不同的用途。当 volatile 用于一个作用域时,Java保证如下:

  1. (适用于Java所有版本)读和写一个 volatile 变量有全局的排序。也就是说每个线程访问一个 volatile 作用域时会在继续执行之前读取它的当前值,而不是(可能)使用一个缓存的值。(但是并不保证经常读写 volatile 作用域时读和写的相对顺序,也就是说通常这并不是有用的线程构建)。
  2. (适用于Java5及其之后的版本) volatile 的读和写建立了一个happens-before关系,类似于申请和释放一个互斥锁[7]

使用volatile会比使用更快,但是在一些情况下它不能工作。volatile使用范围在Java5中得到了扩展,特别是双重检查锁定现在能够正确工作[8]

Ada中的volatile

Ada中,比起关键字,Volatile标记更像是一种指令。“对于volatile对象而言,所有读和更新都会作为一个整体直接执行到内存”[9]

参考

  1. ^ 1.0 1.1 Publication on C++ standards committee website; http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2016.html (页面存档备份,存于互联网档案馆
  2. ^ Volatile Keyword In Visual C++; http://msdn2.microsoft.com/en-us/library/12a04hfd.aspx (页面存档备份,存于互联网档案馆
  3. ^ Linux Kernel Documentation - Why the "volatile" type class should not be used; . [2007-08-17]. (原始内容存档于2007-08-25). 
  4. ^ Volatile: Almost Useless for Multi-Threaded Programming (Intel Software Network); . [2011-08-31]. (原始内容存档于2007-12-13). 
  5. ^ C++ and the Perils of Double-Checked Locking; http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf (页面存档备份,存于互联网档案馆
  6. ^ 存档副本. [2016-02-20]. (原始内容于2012-10-20). 
  7. ^ Section 17.4.4: Synchronization Order . Sun Microsystems. 2005 [2010-11-22]. (原始内容存档于2012-02-14). 
  8. ^ Neil Coffey. . Javamex. [2009-09-19]. (原始内容存档于2021-03-06). 
  9. ^ "C.6 Shared Variable Control" . ISO. 2005 [2010-05-04]. (原始内容存档于2021-03-06). 

外部链接

  • Ada Reference Manual C.6: Shared Variable Control (页面存档备份,存于互联网档案馆

volatile变量, 在程序设计中, 尤其是在c语言, 和java语言中, 使用volatile关键字声明的变量或对象通常具有与优化, 多线程相关的特殊属性, 通常, volatile关键字是用来阻止, 编译器因誤認某段程式碼無法被程式碼本身所改變, 而造成的過度優化, 如在c语言中, volatile关键字可以用来提醒编译器它后面所定义的变量随时有可能改变, 因此编译后的程序每次需要存储或读取这个变量的时候, 都会直接从变量地址中读取数据, 如果没有volatile关键字, 则编译器可能优化读取和存储, 可能暂. 在程序设计中 尤其是在C语言 C C 和Java语言中 使用volatile关键字声明的变量或对象通常具有与优化 多线程相关的特殊属性 通常 volatile关键字是用来阻止 伪 编译器因誤認某段程式碼無法被程式碼本身所改變 而造成的過度優化 如在C语言中 volatile关键字可以用来提醒编译器它后面所定义的变量随时有可能改变 因此编译后的程序每次需要存储或读取这个变量的时候 都会直接从变量地址中读取数据 如果没有volatile关键字 则编译器可能优化读取和存储 可能暂时使用寄存器中的值 如果这个变量由别的程序更新了的话 将出现不一致的现象 在C环境中 volatile关键字的真实定义和适用范围经常被误解 虽然C C 和Java都保留了C中的volatile关键字 但在这些编程语言中volatile的用法和语义却大相径庭 目录 1 C和C 中的volatile 1 1 对用户定义的非基本数据类型使用volatile 1 2 volatile与多线程语义 1 3 C语言中MMIO的例子 1 4 C语言中的优化对比 2 Java中的volatile 3 Ada中的volatile 4 参考 5 外部链接C和C 中的volatile 编辑在C 以及C 中 volatile关键字的作用 1 允许访问内存映射设备 允许在setjmp和longjmp之间使用变量 允许在信号处理函数中使用sig atomic t变量根据相关的标准 C C POSIX WIN32 和目前绝大多数实现 对volatile变量的操作并不是原子的 也不能用来为线程建立严格的happens before关系 volatile关键字就像便携式线程构建一样基本没什么用处 1 2 3 4 5 Visual C 2005 保证volatile变量是一种内存屏障 阻止编译器和CPU重新安排读入和写出语义 6 在先前版本的Visual C 则没有此类保证 在其他方面将指针定义为volatile可能会影响程序的性能 例如 如果指针定义对代码的其他地方可见 强制编译器将指针视为屏障 就会降低程序的性能 这是完全不必要的 对用户定义的非基本数据类型使用volatile 编辑 基本类型的对象用volatile修饰后 仍旧支持所有的操作 加 乘 赋值等 但是 用户定义的非基本类型 class struct union 的对象被volatile修饰后 具有不同行为 只能调用volatile成员函数 即只能访问它的接口的子集 只能通过const cast运算符转为没有volatile修饰的普通对象 即由此可以获得对类型接口的完全访问 volatile性质会传递给它的数据成员 volatile与多线程语义 编辑 临界区内部 通过互斥锁 mutex 保证只有一个线程可以访问 因此临界区内的变量不需要是volatile的 而在临界区外部 被多个线程访问的变量应为volatile 这也符合了volatile的原意 防止编译器缓存 cache 了被多个线程并发用到的变量 volatile对象只能调用volatile成员函数 这意味着应仅对多线程并发安全的成员函数加volatile修饰 这种volatile成员函数可自由用于多线程并发或者重入而不必使用临界区 非volatile的成员函数意味着单线程环境 只应在临界区内调用 在多线程编程中可以令该数据对象的所有成员函数均为普通的非volatile修饰 从而保证了仅在进入临界区 即获得了互斥锁 后把该对象显式转为普通对象之后才能调用该数据对象的成员函数 这种用法避免了编程者的失误 在临界区以外访问共享对象的内容 template lt typename T gt class LockingPtr public LockingPtr volatile T amp obj Mutex amp mtx pObj const cast lt T gt amp obj pMtx amp mtx mtx Lock LockingPtr pMtx gt Unlock T amp operator return pObj T operator gt return pObj private T pObj Mutex pMtx LockingPtr const LockingPtr amp LockingPtr amp operator const LockingPtr amp 对于内建类型 不应直接用volatile 而应把它包装为结构的成员 就可以保护了volatile的结构对象不被不受控制地访问 C语言中MMIO的例子 编辑 在这里例子中 代码将foo的值设置为0 然后开始不断地轮询它的值直到它变成255 static int foo void bar void foo 0 while foo 255 一个执行优化的编译器会提示没有代码能修改foo的值 并假设它永远都只会是0 因此编译器将用类似下列的无限循环替换函数体 void bar optimized void foo 0 while true 但是 foo可能指向一个随时都能被计算机系统其他部分修改的地址 例如一个连接到中央处理器的设备的硬件寄存器 上面的代码永远检测不到这样的修改 如果不使用volatile关键字 编译器将假设当前程序是系统中唯一能改变这个值部分 这是到目前为止最广泛的一种情况 为了阻止编译器像上面那样优化代码 需要使用volatile关键字 static volatile int foo void bar void foo 0 while foo 255 这样修改以后循环条件就不会被优化掉 当值改变的时候系统将会检测到 C语言中的优化对比 编辑 下面的C程序和后面的汇编代码展示了volatile关键字如何影响编译器的输出 这里使用的编译器是GCC 汇编对照不使用volatile 使用volatile include lt stdio h gt int main int a 10 b 100 c 0 d 0 printf d a b a b c b d b printf d c d return 0 include lt stdio h gt int main volatile int a 10 b 100 c 0 d 0 printf d a b a b c b d b printf d c d return 0 gcc O3 S without c o without s gcc S with c o with s file without c section rodata str1 1 aMS progbits 1 LC0 string d text p2align 4 15 globl main type main function main leal 4 esp ecx andl 16 esp pushl 4 ecx pushl ebp movl esp ebp pushl ecx subl 20 esp movl 110 4 esp movl LC0 esp call printf movl 200 4 esp movl LC0 esp call printf addl 20 esp xorl eax eax popl ecx popl ebp leal 4 ecx esp ret size main main ident GCC GNU 4 2 1 20070719 FreeBSD file with c section rodata str1 1 aMS progbits 1 LC0 string d text p2align 4 15 globl main type main function main leal 4 esp ecx andl 16 esp pushl 4 ecx pushl ebp movl esp ebp pushl ecx subl 36 esp movl 10 8 ebp movl 100 12 ebp movl 0 16 ebp movl 0 20 ebp movl 8 ebp edx movl 12 ebp eax movl LC0 esp addl edx eax movl eax 4 esp call printf movl 12 ebp eax movl eax 8 ebp movl 12 ebp eax movl eax 16 ebp movl 12 ebp eax movl eax 20 ebp movl 16 ebp edx movl 20 ebp eax movl LC0 esp addl edx eax movl eax 4 esp call printf addl 36 esp xorl eax eax popl ecx popl ebp leal 4 ecx esp ret size main main ident GCC GNU 4 2 1 20070719 FreeBSD Java中的volatile 编辑Java也支持 volatile 关键字 但它被用于其他不同的用途 当 volatile 用于一个作用域时 Java保证如下 适用于Java所有版本 读和写一个 volatile 变量有全局的排序 也就是说每个线程访问一个 volatile 作用域时会在继续执行之前读取它的当前值 而不是 可能 使用一个缓存的值 但是并不保证经常读写 volatile 作用域时读和写的相对顺序 也就是说通常这并不是有用的线程构建 适用于Java5及其之后的版本 volatile 的读和写建立了一个happens before关系 类似于申请和释放一个互斥锁 7 使用volatile会比使用锁更快 但是在一些情况下它不能工作 volatile使用范围在Java5中得到了扩展 特别是双重检查锁定现在能够正确工作 8 Ada中的volatile 编辑在Ada中 比起关键字 Volatile标记更像是一种指令 对于volatile对象而言 所有读和更新都会作为一个整体直接执行到内存 9 参考 编辑 1 0 1 1 Publication on C standards committee website http www open std org jtc1 sc22 wg21 docs papers 2006 n2016 html 页面存档备份 存于互联网档案馆 Volatile Keyword In Visual C http msdn2 microsoft com en us library 12a04hfd aspx 页面存档备份 存于互联网档案馆 Linux Kernel Documentation Why the volatile type class should not be used 存档副本 2007 08 17 原始内容存档于2007 08 25 Volatile Almost Useless for Multi Threaded Programming Intel Software Network 存档副本 2011 08 31 原始内容存档于2007 12 13 C and the Perils of Double Checked Locking http www aristeia com Papers DDJ Jul Aug 2004 revised pdf 页面存档备份 存于互联网档案馆 存档副本 2016 02 20 原始内容存档于2012 10 20 Section 17 4 4 Synchronization Order The Java Language Specification 3rd Edition Sun Microsystems 2005 2010 11 22 原始内容存档于2012 02 14 Neil Coffey Double checked Locking DCL and how to fix it Javamex 2009 09 19 原始内容存档于2021 03 06 C 6 Shared Variable Control Ada Reference Manual ISO 2005 2010 05 04 原始内容存档于2021 03 06 外部链接 编辑Ada Reference Manual C 6 Shared Variable Control 页面存档备份 存于互联网档案馆 取自 https zh wikipedia org w index php title Volatile变量 amp oldid 72288633, 维基百科,wiki,书籍,书籍,图书馆,

文章

,阅读,下载,免费,免费下载,mp3,视频,mp4,3gp, jpg,jpeg,gif,png,图片,音乐,歌曲,电影,书籍,游戏,游戏。