Log in
updated 11:29 AM UTC, May 4, 2016

Disassembling "volatile" keyword

"volatile" keyword instructs compiler not to optimize any instruction which deals with the volatile variable. The primary purpose of it is in dealing with memory mapped I/O operations effectively.

The Compilers support different optimization levels. Based on specific needs the executable can be optimized for footprint, speed of execution, so forth. Optimization is very useful at times since it removes redundant programming constructs, reorganizes the code to achieve its objective, but it has its own demerits. Aggressive optimization at times may inject side-effects which are very hard to trace.


[Note: The discussion of Compiler optimization techniques is beyond the scope of this article].

Example 1:
To illustrate the side-effects of optimization, consider the below code snippet.

C snippet:

uint32_t volatile *pControlRegister = (uint32_t volatile *)(0xD0000000);
void InitControlRegister(void) {
    *pControlRegister = 1; /* Set Control-Register pin 0 high. */
    delay_ms(1000); /* Wait for a while */
    *pControlRegister = 2; /* Set Control-Register pin 1 high. */
    ...
}

If volatile is not used and with optimization enabled, the compiler may remove "*pControlRegister = 1;" statement, considering it as redundant. Due to the optimization, the Control register's bit-0 might never be set to high, but this is against the programmer's intention. To avoid such side-effects, the pointer should be qualified volatile. The volatile keyword would instruct the compiler not to optimize any references of the qualified variable.

Example 2:
To demystify the "volatile" using disassembly of the source code.

Consider the sample code-snippet given below.

C snippet:

volatile int g_data=0;
int copy_g_data=0;
int _tmain(int argc, _TCHAR* argv[])
{
    g_data=(g_data+10)*11;
    copy_g_data = g_data;
    return 0;
}

The following code snippet shows the disassembly of the above sample code.
The disassembly is generated using MSVC++(PE-2005) with optimization set to Maximize Speed(/O2).

Assembly snippet#1 with g_data qualified as volatile:

volatile int g_data=0;
int copy_g_data=0;

int _tmain(int argc, _TCHAR* argv[])
{
    g_data=(g_data+10)*11;
00401C20  mov         eax,dword ptr [g_data (405128h)] 
00401C25  add         eax,0Ah 
00401C28  imul        eax,eax,0Bh 
00401C2B  mov         dword ptr [g_data (405128h)],eax 
    copy_g_data = g_data;
00401C30  mov         ecx,dword ptr [g_data (405128h)] 
00401C36  mov         dword ptr [copy_g_data (40512Ch)],ecx 
	return 0;
00401C3C  xor         eax,eax 
}
00401C3E  ret              

 Assembly snippet#2 with g_data qualified as non-volatile:

/*volatile*/ int g_data=0;
int copy_g_data=0;

int _tmain(int argc, _TCHAR* argv[])
{
    g_data=(g_data+10)*11;
00401C20  mov         eax,dword ptr [g_data (405128h)]
00401C25  add         eax,0Ah
00401C28  imul        eax,eax,0Bh
00401C2B  mov         dword ptr [g_data (405128h)],eax
    copy_g_data = g_data;
00401C30  mov         dword ptr [copy_g_data (40512Ch)],eax
    return 0;
00401C35  xor         eax,eax
}
00401C37  ret

             
Do you see the difference in disassembly?

In assembly snippet#1, the g_data is volatile, Compiler reads the value of g_data from memory everytime it is referenced in the C code above. It becomes very evident if you look at the following assembly conversion.

      copy_g_data = g_data;

// Read g_data into ecx 
// [Note: eax might not be in sync with g_data, hence not reused].
00401C30  mov         ecx,dword ptr [g_data (405128h)] 
// and then copy ecx to copy_g_data.
00401C36  mov         dword ptr [copy_g_data (40512Ch)],ecx 

In assembly snippet#2, the g_data is non-volatile, Compiler knows that g_data is cached into eax, hence, eax is directly moved into copy_g_data.

    copy_g_data = g_data;

// copy eax into copy_g_data, since eax holds g_data's value.
00401C30  mov         dword ptr [copy_g_data (40512Ch)],eax  

If g_data is memory-mapped, then any relevant I/O peripheral can access and modify g_data by means not known to the program above. This means eax and g_data can go out of sync anytime, the program may end up copying old value of g_data into copy_g_data. This is against the programmer's intention. By qualifying the g_data as volatile, the programmer can ensure that the value is actually read from memory since optimization to all references of g_data are suppressed!

Add a comment (0)

  • Written by Arun Kumar Venugopalan
  • Category: C/C++
  • Hits: 2656
Follow Us on Twitter
Find Us on Facebook
Follow Us on Google
Follow Us on Pinterest