In-depth explanation of the function of volatile modifiers in C language programming _swift

Source: Internet
Author: User
Tags constant current time error code local time modifier modifiers time interval volatile

Volatile reminds the compiler that the variables defined later can change at any time, so the compiled program will read the data directly from the variable address each time it needs to store or read the variable. If there is no volatile keyword, the compiler may optimize read and save, may temporarily use the value in the register, if this variable is updated by another program, there will be inconsistencies. The following examples illustrate. In DSP development, often need to wait for an event to trigger, so often write this program:

Short flag;
void Test ()
{
do1 ();
while (flag==0);
Do2 ();
}

This program waits for the value of the memory variable flag to become 1 (suspected to be 0, somewhat doubtful) before running Do2 (). The value of the variable flag is changed by another program, which may be a hardware interrupt service program. For example: If a button is pressed, it will interrupt the DSP, in the key interrupt program to modify the flag 1, so that the above program can continue to run. However, the compiler does not know that the value of the flag will be modified by another program, so when it is optimized, it is possible to read the value of the flag into a register and wait for that register to become 1. If this optimization is unfortunate, the while loop becomes a dead loop because the contents of the register cannot be modified by the Interrupt service program. In order for the program to read the value of the TRUE flag variable each time, it needs to be defined as the following form:

volatile short flag;

It should be noted that no volatile may work correctly, but it may not work properly after modifying the compiler's optimization level. As a result, the debug version is often normal, but the release version is not normal. So to be safe, add the volatile keyword as long as you wait for another program to modify a variable.

Volatile was meant to be "variable."
because the speed of access registers is faster than RAM, the compiler generally makes optimizations to reduce access to external RAM. Like what:

static int i=0;
int main (void)
{
...
while (1)
{
if (i) do_something ();
}
}
/* Interrupt Service routine. *
/void isr_2 (void)
{
I=1
}

The intention of the program is to call the Do_something function in main when the isr_2 interrupt is generated, but because the compiler determines that I is not modified in the main function, it is possible to perform only one read operation from I to a register. Then each if judgment only uses the "I copy" inside the register, causing the do_something to never be invoked. If the variable is volatile decorated, the compiler guarantees that the read-write operation on this variable will not be optimized (definitely executed). I should also say so in this example.
Generally speaking, volatile is used in several places as follows:
1, the interruption of the service program for other procedures to detect the variables need to add volatile;
2. The volatile should be added to the signs shared between tasks in a multitasking environment;
3, memory mapping hardware registers are usually added volatile description, because each of its reading and writing may be different meaning;
In addition, these situations often have to consider the integrity of the data (a number of interrelated flags read half interrupted rewrite), in 1 can be implemented through the shutdown, 2 can prohibit task scheduling, 3 of the hardware can only rely on good design.
the deep meaning of volatile
volatile is always related to optimization, the compiler has a technology called data flow analysis, analysis of the variables in the program where to assign value, where to use, where the failure, analysis results can be used for constant merging, constant propagation, such as optimization, further can die code elimination. But sometimes these optimizations are not required by the program, and you can use the volatile keyword to disable these optimizations, and the literal meaning of volatile is variable, and it has the following effect:

    1. Volatile variables are not cached in registers between two operations. In multitasking, interrupts, and even setjmp environments, variables may be altered by other programs, and the compiler cannot know for themselves, volatile is telling the compiler this.
    2. Do not make constant consolidation, constant propagation, and so on optimizations, so like the following code:
volatile int i = 1;
if (i > 0) ...

If conditions are not treated as unconditional true.

    • Reading and writing to volatile variables is not optimized. If you assign a value to a variable but do not use it later, the compiler can often omit that assignment, but the processing of memory mapped IO cannot be optimized.

It is said earlier that volatile can guarantee the atomic nature of memory operations, which is not accurate, one, x86 need a lock prefix to ensure atomicity under SMP, second, RISC can not directly operate on the memory, to ensure that the atomicity of other methods, such as Atomic_inc.

For Jiffies, it has been declared as a volatile variable, and I think it's OK to use jiffies++ directly, not necessarily in that complex form, because that doesn't guarantee atomicity.
You may not know. In Pentium and subsequent CPUs, the following two sets of instructions

Inc jiffies 
;;
mov jiffies,%eax
inc%eax
mov%eax, jiffies

function the same, but one instruction is not as fast as three instructions.
compiler optimization →C keyword volatile→memory break descriptor zz

"Memory" is a special, perhaps the most difficult part of the inline assembly. To explain it clearly, first introduce the compiler optimization knowledge, and then look at the C keyword volatile. Finally, look at the descriptor.


Compiler Optimization Introduction
Memory access speed is much less than CPU processing speed, in order to improve the overall performance of the machine, the hardware on the introduction of caching cache, accelerated access to memory. In addition, the implementation of instructions in the modern CPU is not necessarily strictly in accordance with the order of execution, there is no correlation of the instructions can be disorderly execution, in order to fully utilize the instructions of the CPU line, improve the execution speed. These are hardware-level optimizations. Then look at software-level optimization: one that is optimized by the programmer when writing code, and the other by the compiler. Compiler optimization methods commonly used are: to cache memory variables to register, adjust the order of instructions to make full use of the CPU command line, the most common is the reordering of read and write instructions. When tuning conventional memory, these optimizations are transparent and efficient. The solution to the problem caused by compiler optimizations or hardware reordering is to set up a memory barrier (memory barrier) between operations that must be performed in a particular order from the point of view of the hardware (or other processors), and Linux provides a macro to solve the compiler's execution order problem.


void Barrier (void)
This function notifies the compiler to insert a memory barrier, but is not valid for hardware, and the compiled code stores all the modified values in the current CPU registers in memory, and then reads them back from memory when needed.


Memory
With the knowledge above, it's not difficult to understand memory modifies the descriptor, and the memory descriptor tells GCC:


1 do not reorder the inline assembly instruction with the preceding instruction, that is, before executing the inline assembly code, the preceding instructions are executed


2 do not cache variables into registers, because this code may use memory variables, and these memory variables change in unpredictable ways, GCC inserts the necessary code to write back the variable values that are cached to the register, and if you access those variables later, you need to access the memory again.


If the assembly instruction modifies the memory, but GCC itself is unaware of it, because there is no description in the output section, there is a need to add "memory" to the Modify Description section to tell GCC that the memory has been modified, and GCC will know this information before the instruction Inserting the necessary instructions will precede the memory by optimizing the value of the variable in the cache to register, if you want to use these variables to read again later.


Using "volatile" can also achieve this, but we add this keyword before each variable, it is better to use "memory" convenience.

The importance of volatile for embedded programmers is self-evident, the degree of understanding of volatile is often a lot of companies in the recruitment of embedded programmers in the interview as a measure of eligibility criteria one of the reference, why volatile so important? This is because embedded programmers often have to deal with interrupts, the underlying hardware and so on, and these are used volatile, so the embedded programmer must master the use of volatile.

Volatile is a type modifier, as is the case with a const familiar to the reader. Before we begin to explain volatile, let's talk about a function that we'll use next, and the reader who knows how to use the function can skip the explanation section of the function.

Prototype:

int gettimeofday (struct timeval * TV, struct timezone * tz);

Header file

#include <sys/time.h>

Function: Get current time

Return value: If 0 is successfully returned, the failure returns-1, and the error code is stored in the errno.

Gettimeofday () returns the current time to the structure referred to by TV, and the local time zone information is placed in the TZ structure.
The TIMEVAL structure is defined as:

struct timeval{
 long tv_sec; 
 Long tv_usec; 
};

The timezone structure is defined as:

struct timezone{
 int tz_minuteswest; 
 int tz_dsttime; 
};

First, the timeval structure, where the tv_sec is stored in seconds, while Tv_usec stores microseconds. The timezone member variable is rarely used, and in this case it is simply in the Gettimeofday () function that the local time zone information is placed in the structure of the TZ, in which the tz_minuteswest variables are stored and Greenwich The time difference is how many minutes, tz_dsttime the daylight saves the state of the hour. Our main concern here is to focus on the previous member variable timeval, which we do not use here, so we use the Gettimeofday () function to set a parameter to NULL, let's take a look at a simple code.

#include <stdio.h>
#include <sys/time.h>

int main (int argc, char * argv[])
{
 struct Timeval Start,end;
 Gettimeofday (&start, NULL); /* Test Start time *
 /double timeuse;
 Int J;
 for (j=0;j<1000000;j++)
 ;
 Gettimeofday (&end, NULL); /* Test Termination time * *
 timeuse = 1000000 * (end.tv_sec-start.tv_sec) + end.tv_sec-start.tv_sec;
 Timeuse/= 1000000;
printf ("Run Time:%f\n", timeuse);

 return 0;

}
root@ubuntu:/home#./P

Run time is:

0.002736

Now for a simple analysis of the code, by end.tv_sec-start.tv_sec we get the termination time and start time in seconds, and then use the End.tv_sec-start.tv_sec Gets the time interval between the end time and the start time in subtle units. Because of the time unit, so we multiply the result (end.tv_sec-start.tv_sec) by 1000000 to microseconds, then use Timeuse/= 1000000, and convert it to seconds. Now that you know how to test the elapsed time between start and end code through the Gettimeofday () function, let's look at the volatile modifier now.

Usually in the code, to prevent a variable from being changed in unexpected circumstances, we define the variable as volatile, which allows the compiler to "move" the value of the variable. The exact point is that each time you use this variable, you must read the value of the variable again from memory, instead of using the backup stored in the register.

For example, let's talk about the differences in how you compile in Debug and release mode, which is often referred to as a debug version, contains debugging information, and does not make any optimizations that allow programmers to debug programs. Release is known as a publish version, it is often a variety of optimizations, so that the code size and speed of the program is optimal, so that users are very good to use. With a general idea of the difference between debug and release, let's look at a piece of code below.

#include <stdio.h>

void Main ()
{
int a=12;
printf ("A value is:%d\n", a);
__asm {mov dword ptr [ebp-4], 0h}
int b = A;
printf ("B's value is:%d\n", b);
}

Analysis of the above code, we used a sentence __asm {mov dword ptr [ebp-4], 0h} to modify the value of variable a in memory. The difference between Debug and release compilation is explained earlier, so let's look at the results now. Note: Compile and run with VC6, if no special instructions, all in Linux environment. Readers should not forget to choose the mode of compiling and running when compiling.

The results of using the debug mode are:

The value of a is: 
0 Press any 
key to continue 

The results of using release mode are:

The value of a is: 
the value of B is: Press any 
key to continue 

Looking at the results above, we found that the value of B was for 12 after the release mode was optimized, but when the debug mode was used, the value of B was 0. Why is there such a situation? Let's take a look at the following code before we say the answer. Note: Run with VC6 compilation

#include <stdio.h> 
 
void Main () 
{ 
int volatile a=12; 
printf ("A value is:%d\n", a); 
__asm {mov dword ptr [ebp-4], 0h} 
int b = A; 
printf ("B's value is:%d\n", b); 
} 

The results of using the debug mode are:

The value of a is: 
0 Press any 
key to continue 

The results of using release mode are:

The value of a is: 
0 Press any 
key to continue 

We found that this is the same result regardless of whether you use debug mode or Release mode. Now we're going to analyze that, before we start with the difference in the way we compile in debug and release mode.

Analyze the previous code first, because in the debug mode we do not optimize the code, so every time in the code to use a is worth the time from its memory address directly read, so we used the __asm {mov dword ptr [ebp-4], 0h} The statement changes the value of a and then reads directly from memory when using a value. So what we get is the updated a value, but when we run in release mode, we find the value of B before the value of a, not our updated a value, because the compiler optimizes the process. The compiler found that the value of a was not changed again after assigning a value to a so the compiler put a value back in a register, in the subsequent operation we again use a value of the direct operation of the Register, but not to read a memory address, because the speed of reading registers faster than directly read memory speed. This makes the value of a read to the previous 12. Rather than the 0 after the update.

In the second code we use a volatile modifier, in this case, the value of the updated a is obtained in whatever mode, because the volatile modifier tells the compiler not to optimize any of the variables it modifies, and each value is obtained directly from the memory address. From here we can see that for those variables in our code, we'd better use the volatile modifier to get the value of each update. To deepen the impression, let's take a look at the following code.

#include <stdio.h> 
#include <sys/time.h> 
 
int main (int argc, char * argv[]) 
{ 
 struct Timeval Start,end; 
 Gettimeofday (&start, NULL); /* Test Start time * 
 /double timeuse; 
 Int J; 
 for (j=0;j<10000000;j++) 
  ; 
 Gettimeofday (&end, NULL); /* Test Termination time * * 
 timeuse = 1000000 * (end.tv_sec-start.tv_sec) + end.tv_usec-start.tv_usec; 
 Timeuse/= 1000000; 
printf ("Run Time:%f\n", timeuse); 
 
 return 0; 
 
} 

As with the previous test time code, we just increased the number of for () loops.

Let's take a look at our results without using the optimization:

root@ubuntu:/home# gcc time.c-o P 
root@ubuntu:/home#. 
/P Elapsed time: 0.028260 

The optimized run results are used:

root@ubuntu:/home# gcc-o P time.c-o2 
root@ubuntu:/home#. 
/P Elapsed time: 0.000001 

It is clear from the results that the gap is so great, but if we modify Int j to int volatile J in the above code, we'll look at the following code:

#include <stdio.h> 
#include <sys/time.h> 
 
int main (int argc, char * argv[]) 
{ 
 struct Timeval Start,end; 
 Gettimeofday (&start, NULL); /* Test Start time * 
 /double timeuse; 
 int volatile J; 
 for (j=0;j<10000000;j++) 
  ; 
 Gettimeofday (&end, NULL); /* Test Termination time * * 
 timeuse = 1000000 * (end.tv_sec-start.tv_sec) + end.tv_usec-start.tv_usec; 
 Timeuse/= 1000000; 
printf ("Run Time:%f\n", timeuse); 
 
 return 0; 
 
} 

Let's take a look at the results we don't use optimizations are:

root@ubuntu:/home# gcc time.c-o P 
root@ubuntu:/home#. 
/P Elapsed time: 0.027647 

The optimized run results are used:

root@ubuntu:/home# gcc-o P time.c-o2 
root@ubuntu:/home#. 
/P Elapsed time: 0.027390 

We find that the time is almost unchanged, regardless of whether or not the optimization statement is being used at this point, but there is a slight difference, which is caused by the computer itself. So we know by comparing the results of the above one using the volatile with volatile and the following one, using the volatile variable in the use of the optimization statement is for () the loop is not optimized because the for () loop performs an empty operation, Typically, an optimization statement is used so that the for () loop is optimized and not executed at all. It's like the compiler sets the value of I to a number greater than or equal to 10000000 during compilation so that the for () loop statement does not execute. But because we use the volatile, the compiler does not move our value by itself, so the loop body is executed. The reason for this example is to keep the reader in mind that if we define the volatile variable, then it will not be optimized by the compiler.

Of course, what are some of the notable volatile? Because the speed of the access registers is faster than the direct access memory, the compiler typically reduces access to memory, but if you add a variable to the volatile modifier, the compiler guarantees that the read-write operation on the variable will not be optimized. This may be a little abstract, and then look at the following code, here briefly write a few steps.

Main ()

{

  int i=o;

  while (i==0)

  {

     ...

  }

}}

Analysis of the above code, if we do not change the value of I in the while loop body structure, the compiler will back up the value of I to a register in the process of compiling, and the value from that register every time the judgment statement is executed, then this will be a dead loop, but if we do the following modifications:

Main ()

{

  int volatile i=o;

  while (i==0)

  {

     ...

  }

}}

We added a volatile to the front of I, assuming that the while () loop body executes the exact same operation, but this time it cannot be said to be a dead loop, because the compiler will no longer "back up" the value of our I. Every time the judgment is executed, it reads directly from the memory address of I, and exits the loop body once its value has changed.

Finally, the point is that the use of volatile in the actual use of the situation in general have the following points:

1, the interruption of the service program for other procedures to detect the variables need to add volatile;

2. The volatile should be added to the signs shared between tasks in a multitasking environment;

3, memory mapping hardware registers are usually added volatile description, because each of its read and write may have different meanings.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.