1. Why use volatile?

The volatile keyword in C/C++ corresponds to const and is used to modify variables, typically to establish a language-level memory barrier. This is Bjarne Stroustrup's (BS) explanation of the volatile modifier in "The C++ Programming Language":

A volatile specifier is a hint to a compiler that an object may change its value in ways not specified by the language so that aggressive optimizations must be avoided.

The volatile keyword is a type modifier. A variable declared with it indicates that it can be changed by factors unknown to the compiler, such as the operating system, hardware, or other threads. When encountering a variable declared with this keyword, the compiler will no longer optimize the code that accesses the variable, thereby providing stable access to special addresses. The syntax for declaration is:int volatile vInt;When the value of a variable declared with volatile is requested, the system always re-reads the data from the memory where it resides, even if the preceding instruction just read data from that location. Moreover, the read data is immediately saved. For example:

volatile int i=10;
int a = i;
...
// 其他代码,并未明确告诉编译器,对 i 进行过操作
int b = i;

volatile indicates that i may change at any time. Every time it is used, it must be read from the address of i. Therefore, the assembly code generated by the compiler will re-read the data from the address of i and place it into b. The optimized approach, however, is that because the compiler finds that the code between the two reads of i does not operate on i, it automatically places the previously read data into b instead of re-reading from i. In this way, if i is a register variable or represents port data, errors can easily occur. Therefore, volatile can guarantee stable access to special addresses. Note that in VC 6, the general debug mode does not perform code optimization, so the effect of this keyword cannot be seen. Below, by inserting assembly code, we test the impact of the presence or absence of the volatile keyword on the final code of the program. Enter the following code:

Example

#include <stdio.h> void main() { int i = 10; int a = i; printf("i = %d", a); // The purpose of the assembly statement below is to change the value of i in memory // But without letting the compiler know __asm { mov dword ptr [ebp-4], 20h } int b = i; printf("i = %d", b); }

Then, run the program in Debug mode, and the output result is as follows:

i = 10
i = 32

Then, run the program in Release mode, and the output result is as follows:

i = 10
i = 10

The output clearly shows that in Release mode, the compiler optimized the code, and the second time it did not output the correct value of i. Below, let's add the volatile keyword to the declaration of i and see what changes:

Example

#include <stdio.h> void main() { volatile int i = 10; int a = i; printf("i = %d", a); __asm { mov dword ptr [ebp-4], 20h } int b = i; printf("i = %d", b); }

Run the program in Debug and Release versions respectively, and the output in both is:

i = 10
i = 32

This shows that the volatile keyword has played its role. In fact, it is not just the "manipulating the stack via inline assembly" approach that constitutes variable changes unrecognizable by the compiler. More commonly, when multiple threads concurrently access shared variables and one thread changes the value of a variable, how can the changed value be made visible to other threads? Generally speaking, volatile is used in the following places:

  • 1) Variables modified in interrupt service routines that are used for detection by other programs need to be declared volatile;
  • 2) Flags shared between tasks in a multitasking environment should be declared volatile;
  • 3) Memory-mapped hardware registers usually also need the volatile qualifier, because each read from or write to them may have a different meaning;

2. volatile Pointers

Similar to the const modifier — const has the concepts of pointer to const and const pointer — volatile also has corresponding concepts:

Modifying the object pointed to by the pointer; the data is const or volatile:

const char* cpch;
volatile char* vpch;

Note: For VC, this feature is only safe when implemented in VC 8 or later.

The pointer's own value — an integer variable representing an address — is const or volatile:

char* const pchc;
char* volatile pchv;

Note:

  • (1) You can assign a non-volatile int to a volatile int, but you cannot assign a non-volatile object to a volatile object.
  • (2) Besides basic types, user-defined types can also be modified with the volatile type.
  • (3) In C++, a class with a volatile identifier can only access a subset of its interface, a subset controlled by the class's implementer. Users can only use const_cast to gain full access to the type's interface. In addition, volatile, like const, propagates from a class to its members.

3. volatile in Multithreading

Some variables are declared with the volatile keyword. When two threads both need to use a certain variable and the value of that variable will be changed, it should be declared with volatile. The function of this keyword is to prevent the optimizing compiler from loading the variable from memory into a CPU register. If the variable is loaded into a register, then the two threads may use the variable in memory and the variable in a register respectively, which can cause incorrect program execution. volatile means letting the compiler always truly fetch the variable from memory each time it operates on it, rather than using the value already stored in the register, as follows:

volatile  BOOL  bStop  =  FALSE;

(1) In one thread:

while(  !bStop  )  {  ...  }  
bStop  =  FALSE;  
return;    

(2) In another thread, to terminate the above thread loop:

bStop  =  TRUE;  
while(  bStop  );  //等待上面的线程终止,如果bStop不使用volatile申明,那么这个循环将是一个死循环,因为bStop已经读取到了寄存器中,寄存器中bStop的值永远不会变成FALSE,加上volatile,程序在执行时,每次均从内存中读出bStop的值,就不会死循环了。

This keyword is used to set the storage location of an object in memory rather than in a register. Because for ordinary objects, the compiler may place a copy of them in a register to speed up instruction execution, for example in the following code:

...  
int  nMyCounter  =  0;  
for(;  nMyCounter<100;nMyCounter++)  
{  
...  
}  
...

In this code segment, a copy of nMyCounter may be stored in a register (in the loop, testing and operations on nMyCounter are always performed on the value in this register), but there is another code segment that performs an operation like this:nMyCounter -= 1;In this operation, the change to nMyCounter is performed on the nMyCounter in memory, thus giving rise to a phenomenon: the changes to nMyCounter are not synchronized.

Original address: https://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777432.html