C language: the role of the volatile keyword

I. Introduction

1. Introduction to compiler optimization

Because the memory access speed is far lower than the CPU processing speed, in order to improve the overall performance of the machine, a hardware high-speed cache is introduced on the hardware to accelerate the access to the memory. In addition, the execution of instructions in modern CPUs is not necessarily executed in strict order, and instructions without correlation can be executed out of order to make full use of the instruction pipeline of the CPU and increase the execution speed. The above is the optimization of the hardware level .

Look at the optimization at the software level : one is optimized by the programmer when writing the code, and the other is optimized by the compiler. 编译器优化常用的方法有:将内存变量缓存到寄存器;调整指令顺序充分利用CPU指令流水线,常见的是重新排序读写指令。When optimizing conventional memory, these optimizations are transparent and efficient. The solution to problems caused by compiler optimization or hardware reordering is to set a memory barrier between operations that must be performed in a specific order from the perspective of the hardware (or other processors). Linux provides a macro solution. The execution order of the compiler is a problem.

void Barrier(void)

This function informs the compiler to insert a memory barrier, but it is invalid for the hardware. The compiled code will store all the modified values ​​in the current CPU register into the memory, and then read it from the memory again when the data is needed.

2、volatile

Volatile is always related to optimization. The compiler has a technique called data flow analysis, which analyzes where the variables in the program are assigned, where they are used, and where they fail. The analysis results can be used for constant merging, constant propagation and other optimizations, which can be further eliminated. Some code. But sometimes these optimizations are not required by the program and can be used at this time volatile关键字禁止做这些优化.

Two, detailed explanation of volatile

1. Volatile role

The original intention of volatile is “易变的”that because access to registers is much faster than access to memory cells, the compiler generally optimizes to reduce access to memory, but it may read dirty data. 当要求使用volatile声明变量值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据. To be precise, when encountering a variable declared by this keyword, the compiler will no longer optimize the code that accesses the variable, so as to provide stable access to special addresses; if volatile is not used, the compiler will The statement is optimized. (To put it succinctly: the volatile keyword affects the result of the compiler's compilation. The variable declared with volatile indicates that the variable may change at any time. For operations related to the variable, do not perform compilation optimization to avoid errors)

2. Look at two examples

1> 告诉compiler不能做任何优化

For example, to send two commands to a certain address:

int *ip =...; //设备地址
*ip = 1; //第一个指令
*ip = 2; //第二个指令

The above program compiler may be optimized:

int *ip = ...;
*ip = 2;

As a result, the first instruction is lost. If volatile is used, the compiler will not allow any optimizations to ensure the original intent of the program:

volatile int *ip = ...;
*ip = 1;
*ip = 2;

Even if you want the compiler to optimize, it will not merge the two assignment statements into one. It can only do other optimizations.

2> 用volatile定义的变量会在程序外被改变,每次都必须从内存中读取,而不能重复使用放在cache或寄存器中的备份。

E.g:

volatile char a;

a=0;

while(!a){
    
    
//do some things;
}

doother();

如果没有 volatile,doother()不会被执行

3. Several scenarios for using volatile variables

1> 中断服务程序中修改的供其它程序检测的变量需要加volatile;

E.g:

static int i=0;

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

/* Interrupt service routine. */

void ISR_2(void)
{
    
    
      i=1;
}

ISR_2 intention is to program an interrupt is generated, the function call dosomething main function, however, because the compiler determines not changed inside the main function i, it may be performed only once from ithe read operation of a register, and each ifis determined All only use the contents of this register “i副本”, causing dosomething to never be called. If you add the variable volatilemodified, then the compiler reads and writes variables to ensure that this will not be optimized (positive execution). In this example iit should be so stated.

2>多任务环境下各任务间共享的标志应该加volatile

3>存储器映射的硬件寄存器通常也要加voliate,因为每次对它的读写都可能有不同意义。

E.g:

Suppose a device is to be initialized, and a certain register of this device is 0xff800000.

int *output = (unsigned int *)0xff800000;//定义一个IO端口;

int init(void)
{
    
    
	int i;
      
	for(i=0; i< 10; i++){
    
    
		*output = i;
	}
}

After optimization by the compiler, the compiler thinks that the previous loop is nonsense for half a day, and has no effect on the final result, because in the end it just assigns the output pointer to 9, so the compiler finally gives you the compiled code result is equivalent to:

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

If you initialize the external device in the same order as the code above, obviously the optimization process will not achieve the goal. Conversely, if you do not repeat write operations to this port, but repeat read operations, the result is the same. After the compiler is optimized, perhaps your code reads this address only once. However, from the code point of view, there is no problem. At this time in respect of the use volatileto inform the compiler that the variable is an unstable, not to optimize the experience this time variable.

E.g:

volatile  int *output=(volatile unsigned int *)0xff800000;//定义一个I/O端口

In addition, the above several situations often have to be considered at the same time 数据的完整性(the interrelated signs are half-read and interrupted and rewritten) 1中可以通过关中断来实现,2中禁止任务调度,3中则只能依靠硬件的良好设计.

4. Several questions

  • 1) Can a parameter be either const or volatile?

Yes, such as a read-only status register. It is volatile because it can be changed unexpectedly. It is const because the program should not try to modify it.

  • 2) Can a pointer be volatile?

Yes, when an interrupt service routine modifies a pointer to a buffer.

5. The essence of volatile

1> Optimization of the compiler

In this thread, when reading a variable, in order to improve the access speed, the compiler sometimes reads the variable into a register when optimizing; later, when the variable value is taken, the value is directly taken from the register ; When the variable value is changed in this thread, the new value of the variable will be copied to the register at the same time, in order to maintain consistency.

When the value of the variable changes due to other threads, etc., the value of the register will not change accordingly, causing the value read by the application program to be inconsistent with the actual variable value.

When the value of this register is changed due to other threads, etc., the value of the original variable will not change, causing the value read by the application program to be inconsistent with the actual variable value.

2> volatile should be interpreted as “直接存取原始内存地址”more appropriate, "variable" This interpretation is simply a bit misleading.

6. What is wrong with the following function

int square(volatile int *ptr)
{
    
    
	return *ptr * *ptr;
}

The purpose of this program is to return the square of the value pointed to by the pointer ptr. However, since ptr points to a volatile parameter, the compiler will generate code similar to the following:

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

Since the value of *ptr may be unexpectedly changed, a and b may be different. As a result, this code may return a square value that is not what you expected! The correct code is as follows:

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

注意:频繁地使用volatile很可能会增加代码尺寸和降低性能,因此要合理的使用volatile。

Guess you like

Origin blog.csdn.net/houxiaoni01/article/details/105163980