Volatile, the secret of C Language

Source: Internet
Author: User

The importance of volatile is self-evident for Embedded programmers, the understanding of volatile is often used by many companies as a reference to measuring whether a candidate is qualified when recruiting embedded programmers. Why is volatile so important? This is because embedded programmers often have to deal with interruptions, underlying hardware, and these are all involved in volatile, so embedded programmers must master the use of volatile.

In fact, like the const familiar to readers, volatile is a type modifier. Before explaining volatile, Let's explain a function that will be used next. Readers who know how to use this function can skip the description section of this function.

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

Header file: # include <sys/time. h>

Function: gets the current time.

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

Gettimeofday () will return the current time in the structure indicated by TV, and put the information of the local time zone in the structure indicated by tz.

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 ;};

Let's talk about the timeval struct, where TV _sec stores seconds, while TV _usec stores microseconds. The timezone member variables are rarely used. Here, we will briefly describe that their role in the gettimeofday () function is to put the local time zone information in the structure referred to by tz, the tz_minuteswest variable stores the time difference between Greenwich and tz_dsttime for daylight saving. Here, we mainly focus on the previous Member variable timeval, which is not used here. Therefore, when using the gettimeofday () function, we set a parameter to null, let's take a look at a simple piece of 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 end 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 ;}

The running result is:

Root @ Ubuntu:/home #./P Run Time: 0.002736

Now let's analyze the code in a simple way and use end. TV _sec-start. TV _sec we get the interval between the end time and the start time in seconds, and then use end. TV _sec-start. the time interval between the TV _sec termination time and the start time in a subtle unit. Because of the time unit, we are here for (end. TV _sec-start. TV _sec), multiply the result by 1000000, convert it to microseconds, and then use timeuse/= 1000000; convert it to seconds. Now we know how to use the gettimeofday () function to test the running time between start and end codes. Now let's take a look at the volatile modifier.

In code, we usually define a variable as volatile to prevent it from being changed unexpectedly, this makes it impossible for the compiler to automatically "Trigger" the value of this variable. To be accurate, you must re-read the value of this variable from the memory every time you use this variable, instead of using the backup stored in the register.

Before giving an example, let's talk about the differences between the compiling methods in debug and release modes. debug is usually called a debugging version. It contains debugging information without any optimization and facilitates programmers to debug programs. Release is called a release version. It is often optimized to optimize the code size and running speed, so that users can use it well. After learning about the differences between debug and release, let's take a look at a piece of code.

# Include <stdio. h> void main () {int A = 12; printf ("A value: % d \ n", a) ;__ ASM {mov dword ptr [ebp-4], 0 h} int B = A; printf ("the value of B is % d \ n", B );}

First analyze the above Code, we use a sentence _ ASM {mov dword ptr [ebp-4], 0 h} to modify the value of variable A in memory, if you are not clear about the function of this Code, refer to my previous article "stack of the little secrets of C language". I will not explain it too much here. We have already explained the differences between debug and release compilation methods. Now let's take a look at the results. Note: Use vc6 to compile and run the program. Unless otherwise specified, the program is compiled and run in Linux. When compiling, do not forget to select the compiling and running mode.

The result of using the debug mode is:

The value of A is 12B and 0 press any key to continue.

The result of using the release mode is:

The value of A is 12B and 12 press any key to continue.

Looking at the above running results, we found that the value of B is 12 after optimization in the release mode, but the value of B is 0 when debug mode is used. Why is this happening? Let's leave the answer blank, and then look at the following code. Note: Use vc6 for compiling and running

# Include <stdio. h> void main () {int volatile A = 12; printf ("A value: % d \ n", a) ;__ ASM {mov dword ptr [ebp-4], 0 h} int B = A; printf ("the value of B is % d \ n", B );}

The result of using the debug mode is:

The value of A is 12B and 0 press any key to continue.

The result of using the release mode is:

The value of A is 12B and 0 press any key to continue.

In this case, both debug mode and release mode are the same results. Now let's analyze the differences between the compilation methods in debug and release modes.

First, analyze the previous Code. Because the code is not optimized in debug mode, each time a is used in the code, it is directly read from its memory address, so when we use the _ ASM {mov dword ptr [ebp-4], 0 h} statement to change the value of A, then use a value to directly read from the memory, so we get the updated value of A. But when we run it in release mode, we find that the value of B is the value before a, instead of the value after update, this is because the compiler has optimized the process. The compiler finds that the value of A is not changed again after a value is assigned to a, so the compiler backs up the value of a in a register, in subsequent operations, when we use the value again, we will directly operate on this register, instead of reading the memory address of A, because the speed of reading the register is faster than that of Directly Reading the memory. This makes the read a value of the previous 12. Instead of the updated 0.

In the second code, we use a volatile modifier. In this case, whatever mode, we get the value of updated, because the volatile modifier is used to tell the compiler not to optimize the variable it modifies, each time the value is obtained directly from the memory address. From this we can see that for the variables in our code, we 'd better use volatile to get the updated value every time. Let's take a look at the following code to help you better understand it.

# 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 end 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 ;}

Like the code we tested earlier, we only increased the number of for () loops.

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

Root @ Ubuntu:/home # GCC time. C-o proot @ Ubuntu:/home #./P Run Time: 0.028260

Optimized running results are used:

Root @ Ubuntu:/home # gcc-o p time. C-o2root @ Ubuntu:/home #./P Run Time: 0.000001

From the results, we can see that the gap is so big, but if we modify Int J to int volatile J in the code above, 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 volatile J; For (j = 0; j <10000000; j ++); gettimeofday (& End, null ); /* test end 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 ;}

First, let's take a look at the running results without optimization:

Root @ Ubuntu:/home # GCC time. C-o proot @ Ubuntu:/home #./P Run Time: 0.027647

The optimized running result is:

Root @ Ubuntu:/home # gcc-o p time. C-o2root @ Ubuntu:/home #./P Run Time: 0.027390

At this moment, no matter whether or not the optimization statement is run, the time is almost unchanged, but there is a slight difference. This tiny difference is caused by the computer itself. Therefore, we can see from the comparison between the above and the following volatile without using volatile. The variables using volatile are not optimized when the optimization statement is a for () loop, because the for () loop executes an empty operation, the optimization statement is usually used to optimize the for () loop and will not be executed at all. Just like the compiler sets the I value to a value greater than or equal to 10000000 during compilation, so that the for () loop statement will not be executed. However, because volatile is used, the compiler does not automatically manipulate our I value, so the loop body is executed. The reason for this example is to keep in mind that if we define the volatile variable, it will not be optimized by the compiler.

Of course, what else is volatile worth noting? Since the speed of accessing registers is faster than the speed of direct access to memory, the compiler generally reduces access to memory. However, if the variable is modified with volatile, the compiler ensures that the read and write operations on this variable are not optimized. This may be a little abstract. Let's take a look at the following code and write a few simple steps here.

Main ()

{

Int I = O;

While (I = 0)

{

......

}

}

Analyze the above Code. If we do not change the I value in the while loop body structure, the compiler will back up the I value to a register during compilation, each time a judgment statement is executed, the value is taken from this register. This will be an endless loop, but if we make the following changes:

Main ()

{

Int volatile I = O;

While (I = 0)

{

......

}

}

We add a volatile in front of I. Assume that the while () loop body executes the same operation as the previous one, but it cannot be an endless loop at this time, because the compiler will no longer perform the "backup" operation on our I value, it will read it directly from the I memory address every time it executes the judgment, once its value changes, it exits the loop body.

The last point is that the usage of volatile in actual use is roughly as follows:

1. Volatile must be added to the variable modified in the interrupted service program for testing by other programs;

2. Volatile should be added to the labels shared by all tasks in a multi-task environment;

3. Volatile is also required for memory-mapped hardware registers, because each read/write operation may have different meanings.

This is the end of the volatile explanation. Due to my limited level, improper or incorrect content in my blog is inevitable, and I hope readers will criticize and correct me. Readers are also welcome to discuss relevant content. If you are willing to share your comments, please leave your valuable comments.

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.