Detailed explanation of the role of volatile definition in single chip microcomputer

Publisher:limm20032003Latest update time:2011-04-11 Reading articles on mobile phones Scan QR code
Read articles on your mobile phone anytime, anywhere

A variable defined as volatile means that the variable may be changed unexpectedly, so the compiler will not assume the value of the variable. To be precise, the optimizer must be careful to re-read the value of the variable each time it is used, instead of using the copy stored in the register. Here are a few examples of volatile variables:
1). Hardware registers of parallel devices (such as status registers)
2). Non-automatic variables accessed in an interrupt service routine
3). Variables shared by several tasks in a multi-threaded application
People who cannot answer this question will not be hired. I think this is the most basic question that distinguishes C programmers from embedded system programmers. Embedded system programmers often deal with hardware, interrupts, RTOS, etc., all of which require volatile variables. Not understanding the content of volatile can be a disaster.
Assuming that the interviewee answered this question correctly (well, I doubt it will be the case), I will dig a little deeper to see if this guy really understands the full importance of volatile.
1). Can a parameter be both const and volatile? Explain why.
2). Can a pointer be volatile? Explain why.
3). What is wrong with the following function:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
Here are the answers:
1). Yes. An example is 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). Yes. Although this is not very common. An example is when a service routine modifies a pointer to a buffer.
3). There is a trick in this code. The purpose of this code is to return the square of the value pointed to by pointer *ptr, but because *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;
}
Because the value of *ptr can be changed unexpectedly, a and b may be different. As a result, this code may not return the square value you expect! The correct code is as follows:
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
The original meaning of volatile is "volatile". Since register access is faster than RAM, the compiler will generally optimize to reduce the access to external RAM. For example:

static int i=0;

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

/* Interrupt service routine. */
void ISR_2(void)
{
i=1;
}

The original intention of the program is to call the dosomething function in main when the ISR_2 interrupt occurs. However, because the compiler determines that i has not been modified in the main function, it may only execute one read operation from i to a certain register, and then each if judgment only uses the "copy of i" in this register, resulting in dosomething never being called. If the variable is modified with volatile, the compiler guarantees that the read and write operations of this variable will not be optimized (definitely executed). i in this example should also be described in this way.
Generally speaking, volatile is used in the following places:
1. Variables modified in the interrupt service program for detection by other programs need to be added with volatile;
2. Flags shared between tasks in a multi-tasking environment should be added with volatile;
3. Memory-mapped hardware registers usually also need to be described with volatile, because each read and write to it may have different meanings;

In addition, the above situations often require the consideration of data integrity (interrelated flags are interrupted and rewritten halfway through reading). In 1, this can be achieved by disabling interrupts
, in 2, task scheduling can be disabled, and in 3, good hardware design is the only way to achieve this.
The original meaning of volatile is "volatile".
Since register access is faster than RAM, compilers generally optimize to reduce access to external RAM. For example:

static int i=0;

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

/* Interrupt service routine. */
void ISR_2(void)
{
i=1;
}

The original intention of the program is to call the dosomething function in main when the ISR_2 interrupt occurs. However, since the compiler determines that i has not been modified in the main function, it
may only perform a read operation from i to a certain register once, and then each if judgment only uses the "copy of i" in this register, resulting in dosomething never being
called. If the variable is modified with volatile, the compiler guarantees that the read and write operations of this variable will not be optimized (definitely executed). The same should be said for i in this example.

Generally speaking, volatile is used in the following places:

1. Variables modified in the interrupt service program for detection by other programs need to be added with volatile;

2. Flags shared between tasks in a multitasking environment should be added with volatile;

3. Memory-mapped hardware registers usually also need to be declared volatile, because each read or write to them may have a different meaning;

In addition, the above situations often require the consideration of data integrity (interrelated flags are interrupted and rewritten halfway through reading). In 1, this can be achieved by disabling interrupts
, in 2, task scheduling can be disabled, and in 3, good hardware design is the only way to solve this problem.
The key lies in two places:

1. Compiler optimization (please help me understand the following)

In this thread, when reading a variable, in order to improve access speed, the compiler sometimes reads the variable into a register first during optimization; later, when getting the variable value, it directly gets the value 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 to keep it consistent

When the value of a variable is changed by another thread, the value of the register will not change accordingly, causing the value read by the application to be inconsistent with the actual variable value.

When the register value is changed by another thread, the value of the original variable will not change, causing the value read by the application to be inconsistent with the actual variable value.


Here is an inaccurate example:

When distributing salaries, the accountant always called employees to register their bank card numbers. One time, the accountant did not register immediately to save time and used the previously registered bank card number. It happened that an employee's bank card was lost and the bank card number had been reported lost. As a result, the employee could not receive his salary.

Employee - original variable address
Bank card number - backup of the original variable in the register


2. Under what circumstances will it appear (as mentioned in the first post)

1). Hardware registers of parallel devices (such as status registers)
2). Non-automatic variables accessed by an interrupt service subroutine
3). Variables shared by several tasks in a multi-threaded application

Supplement: Volatile should be interpreted as "directly accessing the original memory address". The interpretation of "volatile" is a bit misleading.

"Volatile" is caused by external factors, such as multithreading, interrupts, etc. It does not mean that the variable modified with volatile is "volatile". If there is no external factor, it will not change even if it is defined with volatile.

After defining it with volatile, the variable will not change due to external factors and can be used with confidence. Let's see if the previous explanation (volatile) is misleading.


-------------A brief example is as follows:------------------

The volatile keyword is a type modifier. The type variable declared with it indicates that it can be changed by some factors unknown to the compiler, such as the operating system, hardware, or other threads. When encountering a variable declared with this keyword, the compiler will no longer optimize the code that accesses the variable, thereby providing stable access to special addresses.
Examples of using this keyword are as follows:
int volatile nVint;
>>>>When the value of a variable declared with volatile is required, the system always re-reads the data from the memory where it is located, even if the previous instruction has just read the data from there. And the read data is saved immediately.
For example:
volatile int i=10;
int a = i;
...
//Other codes do not explicitly tell the compiler that i has been operated on
int b = i;
>>>>volatile indicates that i may change at any time, and each time it is used, it must be read from the address of i, so the assembly code generated by the compiler will re-read the data from the address of i and put it in b. The optimization method is that since the compiler finds that the code between the two codes that read data from i has not operated on i, it will automatically put the last read data in b. Instead of re-reading from i. In this way, if i is a register variable or represents a port data, it is easy to make mistakes, so volatile can ensure stable access to special addresses.
>>>>Note that in vc6, the general debug mode does not perform code optimization, so the effect of this keyword cannot be seen. Next, we can insert assembly code to test the impact of the volatile keyword on the final program code:
>>>>First, use classwizard to create a win32 console project, insert a voltest.cpp file, and enter the following code:
>>
#include
void main()
{
int i=10;
int a = i;
printf("i= %d",a);
//The purpose of the following assembly statement is to change the value of i in memory without letting the compiler know
__asm ​​{
mov dword ptr [ebp-4], 20h
}
int b = i;
printf("i= %d",b);
}
Then, run the program in debug version mode, and the output results are as follows:
i = 10
i = 32
Then, run the program in release version mode, and the output results are as follows:
i = 10
i = 10
The output clearly shows that in release mode, the compiler optimized the code and did not output the correct i value the second time. Next, we add the volatile keyword to the declaration of i and see what changes occur:
#include
void main()
{
volatile int i=10;
int a = i;
printf("i= %d",a);
__asm ​​{
mov dword ptr [ebp-4], 20h
}
int b = i;
printf("i= %d",b);
}
Run the program in the debug version and the release version respectively, and the output is:
i = 10
i = 32
This shows that this keyword has played its role!


The variable corresponding to volatile may change without your program knowing.
For example, in a multi-threaded program, multiple programs can manipulate this variable in the memory that is accessed together.
Your own program cannot determine when this variable will change
. For example, it corresponds to a certain state of an external device. When the external device operates, the system changes the value of this variable through the driver and interrupt events, and your program does not know.
For volatile type variables, the system directly extracts them from the corresponding memory every time it uses them, and does not use the original value in the cache to adapt to its unknown changes. The system will not optimize the processing of such variables - obviously because its value may change at any time.

A typical example
is for ( int i=0; i<100000; i++);
This statement is used to test the speed of an empty loop.
However, the compiler will definitely optimize it and not execute it at all.
If you write
for ( volatile int i=0; i<100000; i++);
it will execute.

Reference address:Detailed explanation of the role of volatile definition in single chip microcomputer

Previous article:C language source code for simulating serial port
Next article:10 simple digital filtering algorithms (C language source code)

Latest Microcontroller Articles
  • Download from the Internet--ARM Getting Started Notes
    A brief introduction: From today on, the ARM notebook of the rookie is open, and it can be regarded as a place to store these notes. Why publish it? Maybe you are interested in it. In fact, the reason for these notes is ...
  • Learn ARM development(22)
    Turning off and on interrupts Interrupts are an efficient dialogue mechanism, but sometimes you don't want to interrupt the program while it is running. For example, when you are printing something, the program suddenly interrupts and another ...
  • Learn ARM development(21)
    First, declare the task pointer, because it will be used later. Task pointer volatile TASK_TCB* volatile g_pCurrentTask = NULL;volatile TASK_TCB* vol ...
  • Learn ARM development(20)
    With the previous Tick interrupt, the basic task switching conditions are ready. However, this "easterly" is also difficult to understand. Only through continuous practice can we understand it. ...
  • Learn ARM development(19)
    After many days of hard work, I finally got the interrupt working. But in order to allow RTOS to use timer interrupts, what kind of interrupts can be implemented in S3C44B0? There are two methods in S3C44B0. ...
  • Learn ARM development(14)
  • Learn ARM development(15)
  • Learn ARM development(16)
  • Learn ARM development(17)
Change More Related Popular Components

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
circle

About Us Customer Service Contact Information Datasheet Sitemap LatestNews


Room 1530, 15th Floor, Building B, No.18 Zhongguancun Street, Haidian District, Beijing, Postal Code: 100190 China Telephone: 008610 8235 0740

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号