Reprinted from: light-city.club/sc/basic_co…

1.volatile

Variables that are volatile have observable side effects when read or written to them. And these observable side effects are determined by factors outside the program.

2. Volatile application

(1) Hardware registers of parallel devices (such as status registers). Suppose you want to initialize a device whose register is 0xFF800000.

int  *output = (unsigned  int *)0xff800000; // Define an IO port;
int   init(void)  
{  
    int i;  
    for(i=0; i<10;i++)
    {  
    *output = i;  
    }  
}
Copy the code

The compiler optimised the output pointer so that the output pointer was set to 9.

int  init(void)  
{  
    *output = 9;  
}
Copy the code

If you initialize the external device in the same order as the code above, the optimization process will not do the job. On the other hand, if you read the address over and over again instead of writing it over and over again, the result is the same, and the compiler optimizes your code to read the address only once. However, there is no problem from a code perspective. Use volatile to notify the compiler that the variable is unstable and do not optimize when it is encountered.

(2) a variable accessed in an interrupt service subroutine;

static int i=0;

int main(a)
{
    while(1)
    {
    if(i) dosomething();
    }
}

/* Interrupt service routine */
void IRS(a)
{
    i=1;
}
Copy the code

The purpose of the above example program is for the interrupt service subroutine IRS to respond to the interrupt by changing the program variable I so that the dosomething function is called in main. However, since the compiler determines that I has not been changed in main, it may only perform one read from I to a register. Then each if judgment only uses the “I copy” in this register, causing dosomething to never be called. By volatile, the compiler guarantees that no read or write operations on variable I will be optimized, ensuring that changes made to variable I by an external program will be perceived in the original program.

(3) Variables shared by multiple tasks in multi-threaded applications. When a variable is shared by multiple threads and its value is changed by one thread, it should be declared as volatile. The function prevents compiler optimizations from loading variables from memory into CPU registers. When a variable is changed in one thread, it is not synchronized to another thread, causing errors in the program. Volatile means that each time the compiler operates on a variable, it must actually get it out of memory, rather than using a value already in a register. The following is an example:

volatile  bool bStop=false;  //bStop is shared global variable
// The first thread
void threadFunc1(a)
{...while(! bStop){... }}// The second thread terminates the thread loop above
void threadFunc2(a)
{... bStop =true;
}
Copy the code

To terminate the first thread loop through the second thread, if bStop is not volatile, the loop will be dead because bStop has been read into the register, and the value of bStop in the register will never become FALSE. With volatile, the program will execute, Each time the value of bStop is read from memory, the loop will not be infinite.

Knowing how volatile works is a good way to distinguish C/C++ programmers from embedded developers. Embedded guys often deal with hardware, interrupts, RTOS, and so on that require volatile variables, and not knowing that volatile is a programming disaster.

3. Common problems with Volatile

The following questions are intended to determine whether the interviewer is aware of volatile. (1) Can a parameter be both const and volatile? Why is that? You can. One example is a read-only status register. It is volatile because it can be changed unexpectedly. It’s const because the program shouldn’t try to modify it.

(2) Can a pointer be volatile? Why is that? You can. Although it’s not very common. An example is when an interrupt service subroutine fixes a pointer to a buffer.

(3) What is wrong with the following function?

int square(volatile int *ptr) 
{ 
return *ptr * *ptr; 
} 
Copy the code

The purpose of this code is to return the square of the value to which the PTR points, but since the PTR points to a volatile parameter, the compiler produces code similar to the following:

int square(volatile int *ptr) 
{ 
int a,b; 
a = *ptr; 
b = *ptr; 
return a * b; 
} 
Copy the code

Since the value of * PTR can change unexpectedly, a and B can be different. As a result, this code may not return the square you expected! The correct code is as follows:

long square(volatile int *ptr) 
{ 
int a=*ptr; 
return a * a; 
} 
Copy the code

4. Use volatile

  • The volatile keyword is a type modifier that declares a type variable to indicate that it can be changed by some unknown factor (the operating system, hardware, other threads, and so on) that is unknown to the compiler. So using volatile tells the compiler that such objects should not be optimized.
  • Variables declared by volatile must be fetched from memory each time they are accessed. (Variables that are not volatile may be taken from CPU registers due to compiler optimizations.)
  • Const can be volatile (such as a read-only status register)
  • Pointers can be volatile