volatile and effect of optimization
Type Qualifiers in C
In C programming, type qualifiers are used to specify the type and memory location of a variable. The following are the type qualifiers in C:
1. const: specifies that the value of a variable cannot be modified.
const int x = 20; // x is a constant integer with a value of 20
2. volatile: specifies that the value of a variable can change unexpectedly, and prevents the compiler from optimizing its access.
volatile int y = 30; // y is a volatile integer with a value of 30
Volatile
Let’s understand the next type qualifier, which is ‘volatile’.
volatile is a type qualifier in ‘C’ used with variables to instruct the compiler not to invoke any optimization on the variable operation.
What is a variable operation?
Variable operation is either read or write.
- It tells the compiler that the value of the variable may change at any time with or without the programmer’s consent. So, the compiler turns off optimizing the read-write operations on variables which are declared using the volatile keyword.
- Volatile is very much helpful in embedded systems code to reduce the bug.
Now let’s see some examples of the volatile effect. For that, let’s create one project.
I created the project for my target STM32 project. I created 2 variables. One is uint8_t data1; and another variable is uint8_t data2;
I initialize data1 to 50 and data2 = data1.
#include<stdint.h> int main(void) { uint8_t data1; uint8_t data2; data1 = 50; data2 = data1; data2 = data1; for(;;); }
Here our goal is to understand the ‘volatile’ keyword effect.
I copied data1 into data2, and the same code once again data2 = data1.
Here you can see that, data2 = data1 is a redundant ‘C’ statement, this is not required here. Because the data is already copied into data2. So, executing one more time doesn’t make any sense.
What would you do if you are a compiler?
You would generate instructions only for the data2 = data1(line 18) ‘C’ statement. So, you would be ignoring this(line 19) ‘C’ statement. That’s one type of optimization. So, you just optimized to decrease the number of instructions.
But let’s see what the compiler does. The code is actually in the O0 optimization level and let’s see whether the compiler really generates instructions or not.
Let’s check the disassembly (Figure 1). For data1, we are actually putting 50 into data1. There is the store instruction, 50 is stored in the data1 variable. That’s correct.
And then copying data from data1 to data2. First, there is a load instruction, that is the value is loaded from the memory location to the r3 register and after that, there is a store instruction, the value is stored into the variable data2.
And again it generated the same load and store instructions. So, these are actually redundant instructions. These instructions are not required, but still the compiler generated them, because we are using the O0 optimization level.
Let’s see how the disassembly looks in the O1 optimization level, as shown in Figure 2.
In the main function, the compiler removed all the instructions, and code was not generated for all those ‘C’ statements. Why is that?
For that, you have to again think in a compiler way. Of course, data2 = data1 (line 20) is a redundant ‘C’ statement. So, if you remove that statement, then the remaining statements are left. Again, here all those remaining statements are redundant ‘C’ statements. Why?
Because you have created variables but you have not used those variables in the program. Data1 and data2 are unused variables.
Why do you create variables in the program?
You create variables in the program to use it in your program to do some operation, to pass some argument, or to receive some argument.
So, here you are not doing anything like that. You have just created the variables, you just initialize them, and you just left it. All these are actually unused variables. That’s why the compiler didn’t generate any instructions for them. That’s why you see only one instruction in the main that is for this for loop statement.
Now the question is, Being in higher optimization level how to tell the compiler not to optimize operations on certain variables?
We do that by using the ‘volatile’ keyword.
Tell the compiler that don’t do any optimizations on those variable operations. So, you can tell that using the ‘volatile’ keyword.
Again I am mentioning the volatile keyword after the type specifier(as shown in Figure 3). Now, here data1 is a volatile data of uint8, and data2 is also a volatile data of uint8.
Let’s compile and observe the disassembly.
The compiler didn’t optimize the operations on ‘data1’ and ‘data2’ variables. Now you can see the instructions are generated for data1 and data2 operations.
What is the value of data2?
It is 50. data1 is 50. That’s correct.
By using volatile qualifier, you enforce the compiler not to do any optimization on the variables operations(read/write), which are declared as volatile.
Let’s again examine this code at the O3 optimization level.
O3 optimization level is shown in Figure 4.
Even though O3 is a very aggressive optimization, look at the code generated for these operations(Figure 4 – blue color shade). They are not optimized.
The takeaway from this lecture is very very simple by using volatile keyword. So, you can enforce the compiler not to do any optimization on a variables operation. And you will understand and analyze more on this feature in the coming articles.
FastBit Embedded Brain Academy Courses
Click here: https://fastbitlab.com/course1