Summary of const volatile restrict in C language

  • 2020-04-02 01:56:00
  • OfStack

1. The const

Variable declaration with keyword const, Means that the value of the variable cannot be changed by assignment, increment, or decrement This is an obvious point. Using const for Pointers is a little more complicated, because you have to distinguish between making the pointer itself const and the value that the pointer points to being const, and the following declaration means that the value that the pf points to must be constant

Constfloat * pf; Pf is variable and can point to another const or non-const value; In contrast, the following declaration states that pf cannot be changed and that the values that pf points to can be changed:

Float * const pf;

Finally, of course, there can be declarations that can neither change the value of the pointer nor the value of the pointer:

Constfloat * const pf;

Note that there is a third way to place the const keyword:

Float const * pf; // is equivalent to constfloat * pf;

The bottom line is that a const anywhere to the left of * makes the data constant, and a const to the right of * makes the pointer itself const

One more thing to note about the use of const in global data:

Using global variables is considered a risky approach, making it possible for data to be incorrectly modified in any part of the program. So it makes sense to use const for global data.

However, be careful about sharing const data between files, and there are two strategies you can use. One is to follow the usual rules for external variables, defining declarations in one file and referencing declarations in other files (using the keyword extern).

Const double PI = 3.14159;

Extern const dounle PI;

Another approach is to place global variables in an include file, where it is important to note that you must use static external storage classes

Static const double PI = 3.14159;

# include "constant. H".

H # include "constant."

If you do not use the keyword static, including constant.h in the files file1.c and file2.c will cause the definition that each file has the same identifier to declare that the ANSI standard does not support this (some compilers do). By using static, you're essentially giving each file a separate copy of the data. If the file wants to use that data to talk to another file, that's not possible because each file can only see its own copy, but since the data is immutable, that's not a problem. The advantage of using header files is that you don't have to worry about making a definition declaration in one file and a reference declaration in another. The disadvantage is that you copy the data, which can be a problem if the constant is large.

2. The volatile

The qualifier volatile tells the compiler that the variable can be changed by other agents as well as by a program. Typically it is used for data Shared between hardware addresses and other programs running in parallel. For example, an address might hold the current clock information. No matter what the program does, the address will change over time. In another case, an address is used to receive information from another computer;

Syntax and const:

Volatile int a; //a is a variable position

Volatile int * pf; //pf points to a variable location

The reason for using volatile as a keyword is that it makes it easier for the compiler to optimize.

If you have the following code:

Va = x;

// some code that doesn't use x

Vb = x;

A smart compiler might notice that you are using x twice, but without changing its value, it will temporarily store x in a register, and then, when vb is main x, it will save time by getting the value of x from the register rather than the original memory location. This process is called caching. Caching is usually a good optimization, but not if the other proxy between two statements changes the value of x. If the volatile keyword is not specified, the compiler has no way to know if this change is possible, so for safety reasons, the compiler does not use caching. That was the case before ANSI. Now, if the volatile keyword is not used in the declaration, the compiler can assume that a value has not changed during use, and it can try to optimize the code. In short, volatile makes each read read directly from memory rather than from the cache.

You might be surprised to learn that const and volatile can be used together, but they can. For example, a hardware clock cannot be changed by a program, which makes it const, but it is changed by an agent outside the program, which makes it volatile, so you can use them all at once. The order doesn't matter:

Const volatile time;

Volatile indicates that the value of a variable may change externally, and the optimizer must carefully re-read the value of that variable each time it is used, rather than using a backup stored in a register. It can be applied to base classes such as int,char,long... It also applies to C structures and C++ classes. When a structure or class object is treated with volatile, all members of the structure or class are treated as volatile.

This keyword is often used in multithreaded environments because when writing multithreaded programs, the same variable may be modified by multiple threads, and the program synchronizes the threads through this variable.

Simple example:


DWORD __stdcall threadFunc(LPVOID signal)
{
int* intSignal=reinterdivt_cast(signal);
*intSignal=2;
while(*intSignal!=1)
sleep(1000);
return 0;
}

The thread starts with intSignal set to 2 and then loops until it exits when intSignal is 1. Obviously, the value of intSignal must be changed externally or the thread will not exit. However, when the thread is actually running, it will not exit, even if the value of it is changed to 1 in the external, take a look at the corresponding pseudo-assembly code to see:

Mov ax, signal
Label:
If (ax! = 1)
Goto label

For the C compiler, it does not know that the value will be modified by another thread. Naturally, it is cached in a register. The C compiler has no concept of threading, so volatile is needed. Volatile means that the value may change outside the current thread. That is, we are going to prefix the intSignal in threadFunc with the volatile keyword. At this point, the compiler knows that the value of the variable will change externally, so it reads it again every time it accesses the variable, and the loop becomes as shown in the following pseudo-code:
Label:
Mov ax, signal
If (ax! = 1)
Goto label

Note: an argument can be both const and volatile, volatile because it can change unexpectedly. It's const because the program shouldn't try to change it.

3. The restrict

The keyword restrict enhances computational support by allowing the compiler to optimize certain types of code. Remember, it can only be used with Pointers and indicates that Pointers are the only and original way to access a data object. To understand why we do this, we need to look at some examples:


intar[10];
int* restrict restar = (int*)malloc(10*sizeof(int));
int* par = ar;

Here, pointer restar is the only and initial way to access the memory allocated by malloc, so it is declared restrict. However, the par pointer is neither the original nor the only way to access the data in the array ar, so the restrict qualifier is not used. Now consider the following more complex example, where n is an int

for(n= 0;n < 10;n++)
{
par[n]+= 5;
restar[n]+= 5;
ar[n]*= 2;
par[n]+= 3;
restar[n]+= 3;
}

Knowing that restar is the only initial way to access the data it points to, the compiler can replace the two statements that contain restar with one that has the same effect

Restar [n] + = 8;

However, reducing the statement of two compute pars to one results in an error because ar changes the value of the data between the two accesses to the par. Without the keyword restrict, the compiler will have to come up with a worse form, and with it it can safely look for shortcuts to computing. Keywords can be used as qualifiers for pointer function parameters, which means that the compiler can assume that there are no other markers in the function body to modify the data to which the pointer is pointing, so it can try to optimize the code, or vice versa. Take a look at two functions in the C library under the C99 standard, which copy bytes from one location to another

Void *memcpy(void* restrict s1,const void* restrict s2,size_t n);

Void *memmove(void* s1,const void* s2,size_t);

Memcpy requires that the positions of two Pointers not overlap, but memmove does not. Declaring s1, s2 as restrict means that each pointer is unique access to the corresponding data, so they cannot access the same block of data. This satisfies the requirement that there should be no overlap.

The keyword restrict has two readers: the compiler, which tells the compiler that it is free to make certain assumptions about optimization. Another reader is the user, who tells the user to use only the parameters that qualify. In general, the compiler has no way to check that you're following this restriction, and if you defy it, you're risking yourself.


Related articles: