Analysis of C language volatile and const keywords

volatile

1. Keyword explanation:

The original meaning of volatile is "volatile"
because the access to the register is much faster than the access to the memory unit, so the compiler generally optimizes to reduce the access to the memory, but it may read dirty data. When volatile is required to declare the value of a variable, the system always reads data from the memory where it is located, even if the previous instruction has just read data from it. 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)

Generally speaking, volatile is used in the following places:
1) Variables modified in the interrupt service program for detection by other programs need to add volatile;
2) Flags shared between tasks in a multitasking environment should add volatile;
3) Memory The mapped hardware register usually also needs to add volatile description, because every time it is read and written, it may have different meanings.
Without explaining why volatile is used in these situations, let's take a look at the origin:
hardware
Since memory access speed is far lower than CPU, in order to improve performance, hardware cache Cache is introduced in the hardware to accelerate memory access. The execution of instructions in the CPU is not necessarily executed in strict order. If there are no relevant instructions, they can be executed out of order to make full use of the CPU's instruction pipeline (cascade) to increase the execution speed.
Software
first-level optimization: one is optimized by the programmer when writing the code, and the other is optimized by the compiler. The commonly used methods of compiler optimization are: cache memory variables to registers; adjust the order of instructions to make full use of the CPU instruction pipeline, and the common one is to reorder read and write instructions. When optimizing conventional memory, these optimizations are transparent and efficient. The solution to problems caused by compiler optimization or hardware reordering is to set up a memory barrier between operations that must be performed in a specific order from the perspective of the hardware (or other processor). **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 registers into the memory. When the data is needed, Read from the memory again.
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, and some can be further eliminated. Code. But sometimes these optimizations are not what the program needs, then you can use the volatile keyword to prohibit these optimizations.
2. A simple example to explain why volatile should be used
1) Tell the compiler that no optimization can be done

int i;
i=1;
i=2;
i=3;

After the compiler is compiled, it will be optimized as:

int i;
i=3;

Cause the assigned value 1, 2 to be optimized away. In order to ensure the original intention, it should be like this:

volatile int i;
i=1;
i=2;
i=3;

2) Variables defined by volatile will be changed outside the program, and must be read from memory each time, and the backups placed in the cache or registers cannot be reused.
Condition : In multitasking (in multithreading), which is the second point mentioned above, there is a flag bit in multithreading, and it is possible to modify it

volatile int  a;
a=0;
while(!a){
    
    
do some things;
}
init();

If other tasks do not modify a, the init() function will not be executed, and it will also be executed according to the programmer's requirements, and volatile is not required. On the contrary, if the value of variable a is modified in other tasks, real-time is required To update the data in the register, volatile must be added.
3. Use examples to explain the three scenarios where volatile is used:
1) Variables modified in the interrupt service program for detection by other programs need to be volatile

static int a=0;
int main(void)
{
    
    
     while (1){
    
    
if (a) 
dosomething();
              }void IRQ(void)//中断函数
{
    
    
      a=1;
}

The original intention of the program is to call the dosomething function in the main function when the IRQ interrupt is generated. However, since the compiler judges that a has not been modified in the main function, it may only perform a read operation from a to a certain register, and then every The second if judgment only uses the "a copy" in this register, causing dosomething to never be called. If the variable is modified with volatile, the compiler guarantees that the read and write operations on this variable will not be optimized (definitely executed).
The correct code is as follows

volatile static int a=0;
int main(void)
{
    
    
     while (1){
    
    
if (a) 
dosomething();
              }void IRQ(void)//中断函数
{
    
    
      a=1;
}

2) The flags shared between tasks in a multitasking environment should add volatile.
As explained in the above example,
3) Memory-mapped hardware registers, because each reading and writing to it may have different meanings.
Suppose a device is to be initialized, and a certain register address of this device is 0xff800000.

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

After compilation, the compiler thinks that the previous loop is useless for a long time and has no effect on the final result, because in the end it just assigns the output pointer to 4, so the compiler result is equivalent to:

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

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, volatile should be used to inform the compiler that this variable is unstable, and do not optimize when encountering this variable.

volatile  int *output=(volatile  int *)0xff800000;

The above three situations can be shielded by using these three methods respectively.
1) It can be realized by turning off interrupts.
2) Task scheduling is prohibited.
3) It can only rely on good hardware design.
The question is
1) Can a parameter be either const or volatile?
Yes, for example 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.
Variables modified by the keyword volatile must be searched for the corresponding memory address every time they are accessed, because they may be modified at any time. Modified by const can only indicate that this cannot be modified by the programmer, but may be modified by the system.
2) Can a pointer be volatile?
Yes, when a middle service subroutine modifies a pointer to a buffer.
4. The essence of volatile:
1) 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 retrieved , 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.

2) 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.

3) When the value of the register changes due to other threads, etc., the value of the original variable will not change, resulting in inconsistencies between the value read by the application and the actual variable value.
Give an example again

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

The purpose is to return the square of the value pointed to by the pointer ptr. 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 (if there are no other threads or external conditions change *ptr, then this sentence is correct!). As a result, this code may not return the square value you expected! The correct code is as follows:

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

Note: Frequent use of volatile is likely to increase code size and reduce performance, so use volatile reasonably.

Summary:
Volatile variables have two functions:
1) Tell the compiler not to optimize;
2) Tell the system to always take the address of the variable from the memory, instead of taking the value of the variable from the cache or register (with volatile and without volatile system Will generate cache)

const

Explanation The
keyword const is used to define constants. If a variable is modified by const, its value cannot be changed anymore. It can protect the modified things, prevent accidental modification, and enhance the robustness of the program.
Compared with precompiled instructions, const Modifiers have the following advantages:
1) The precompiled instructions only perform simple replacement of values ​​and cannot perform type checking. For
checking:
look at const used to modify constant static strings, for example:
const char* str="fdsafdsa";
if Without the const modification, we may write str[4]='x' statement later, intentionally or unintentionally, which will result in the assignment of the read-only memory area, and the program will immediately terminate abnormally. This error can be checked out when it is compiled, which is the advantage of using const. Let logic errors be found at compile time.

2) It can protect the modified things, prevent accidental modification, and enhance the robustness of the program.
3) The compiler usually does not allocate storage space for ordinary const constants, but saves them in the symbol table, which makes it a compile-time Constants, without the operation of storing and reading memory, make it very efficient.
4) The scope of the macro definition is limited to the current file. By default, const objects are only valid in files. When const variables with the same name appear in multiple files, it is equivalent to defining independent variables in different files. If you want to share const objects between multiple files, you must add the extern keyword before the variable definition (both in the declaration and definition).
1.const modified variable

const int n=1;
int const n=1;

These two ways of writing are the same. They both mean that the value of variable n cannot be changed. It should be noted that when using const to modify the variable, the face change must be initialized, otherwise the assignment will not be possible afterwards.
2. Constant pointer and pointer constant
can be expressed as follows

const int * n;
int const * n;

1) The constant pointer says that the value of the variable cannot be changed through this pointer, but the value of the variable can still be changed through other references.

int i=5;
const int* n=&i;
i=6;

The value pointed to by the constant pointer cannot be changed, but this does not mean that the pointer itself cannot be changed, and the constant pointer can point to other addresses.

int a=5;
int b=6;
const int* n=&a;
n=&b;

2) Pointer constant refers to the pointer itself is a constant, can not point to other addresses

int *const n;

It should be noted that the address pointed to by the pointer constant cannot be changed, but the value stored in the address can be changed and can be modified by other pointers to the changed address. The key to distinguish between constant pointer and pointer constant is the position of the asterisk. We use the asterisk as the dividing line. If const is on the left side of the asterisk, it is a constant pointer, and if const is on the right side of the asterisk, it is a pointer constant. If we read the asterisk as a "pointer" and const as a "constant", the content is exactly the same.

int const*n;是常量指针
int*const n;是指针常量

A constant pointer to a constant is a combination of the above two. The position pointed to by the pointer cannot be changed and the value of the variable cannot be changed through this pointer, but the value of the variable can still be changed through other ordinary pointers.

const int* const p;

3)
Modify the parameters of the function 1) Prevent modification of the content pointed to by the pointer
2) Prevent modification of the address pointed to by the pointer
3) Combination of the above two.
4) Modification of the return value of a function
If the return value of a function in the "pointer transfer" mode is modified with const, the content of the function return value (pointer) cannot be modified, and the return value can only be assigned to the same with const modified Type pointer.
5)
Modify global variables. The scope of global variables is the entire file. We should try to avoid using global variables, because once a function changes the value of global variables, it will also affect other functions that reference this variable, resulting in the exception of bugs It is difficult to find that if we must use global variables, we should try to use const modifiers to modify, so as to prevent unnecessary human modification, the method used is the same as that of local variables.
3. Some suggestions
for using const 1) Use const boldly, which will bring you endless benefits, but the premise is that you must figure out the reason;
2) To avoid the most general assignment errors, such as assigning const variables , See the thinking questions for details;
3) The use of const in parameters should use references or pointers instead of general object instances, for the same reasons as above;
4) The three usages of const in member functions (parameters, return values, functions) are very important Good use;
5) Do not easily set the return value type of the function as const;
6) Except for overloaded operators, generally do not set the return value type as a const reference to an object;
7) Any data that will not be modified All member functions should be declared as const type.

Summary of const and volatile

1. An object can be modified by const and volatile at the same time, indicating that this object embodies constant semantics, but at the same time may be modified by unexpected circumstances in the program context where the current object is located.
2. The variable modified by the keyword volatile must be searched for the corresponding memory address every time it is accessed, because it may be modified at any time. Modified by const can only indicate that this cannot be modified by the programmer, but may be modified by the system.
3. const and volatile are type modifiers, with similar syntax, used for variable or function parameter declarations, and can also restrict non-static member functions. The type variable declared with it can be changed by some factors unknown to the compiler, such as the operating system, hardware, or other threads. When encountering the variable declared by this keyword, the compiler no longer optimizes the code that accesses the variable, which can provide stable access to special addresses and prevent the compiler from adjusting the order of instructions for operating volatile variables. When the value of a variable declared by volatile is required, the system always reads data from the memory where it is located, even if the previous instruction has just read data from it. And the data read is saved immediately.
4. Volatile pointer: 1. Modify the object pointed to by the pointer, and the data is const or volatile: const char* cpch; volatile char * vpch;; 2. The value of the pointer itself-an integer variable representing the address, which is const or Volatile: char* const pchc; char* volatile vchc;.

Guess you like

Origin blog.csdn.net/weixin_42271802/article/details/105973024