Академический Документы
Профессиональный Документы
Культура Документы
}
...
}
This code may work with optimization turned off. But almost all the optimizing c
ompiler would optimize this code to something which is not intended here. Becaus
e the compiler doesn't even have any hint that etx_rcvd can be changed outside t
he code somewhere( as we saw within the serial port ISR). So the compiler assume
s the expression !ext_rcvdwould always be true and would replace the code with in
finite loop. Consequently the system would never be able to exit the while loop.
All the code after the while loop may even be removed by the optimizer or never
be reached by the program. Some compiler may throw a warning, or some may not,
depends completely on the particular compiler.
The solution is to declare the variable etx_rcvd to be volatile. Then all of you
r problems (well, some of them anyway) will disappear.
Multi-threaded applications
Often tasks/threads involved in a multi-threaded application communicate via a s
hared memory location i.e. through a global variable. Well, a compiler does not
have any idea about preemptive scheduling or to say, context switching or whatso
ever. So this is sort of same problem as we discussed in the case of an interrup
t service routine changing the peripheral memory register. Embedded Systems Prog
rammer has to take care that all shared global variables in an multi threaded en
vironment be declared volatile. For example:
int cntr;
void task1(void)
{
cntr = 0;
while (cntr == 0)
{
sleep(1);
}
...
}
void task2(void)
{
...
cntr++;
sleep(10);
...
}
This code will likely fail once the compiler's optimizer is enabled. Declaring '
cntr' to be volatile is the proper way to solve the problem.
Some compilers allow you to implicitly declare all variables as volatile. Resist
this temptation, since it is essentially a substitute for thought. It also lead
s to potentially less efficient code.
Can you have constant volatile variable? You can have a constant pointer to a vo
latile variable but not a constant volatile variable.
Consider the following two blocks of a program, where second block is the same a
s first but with volatile keyword. Gray text between lines of C code means i386/
AMD64 assembler compiled from this code.
{
BOOL flag = TRUE;
while( flag );
repeat:
jmp repeat
}
{
volatile BOOL flag = TRUE;
movdword ptr [flag], 1
while( flag );
repeat:
moveax, dword ptr [flag]
testeax, eax
jnerepeat
}
In first block variable 'flag' could be cached by compiler into a CPU register,
because it does not havevolatile qualifier. Because no one will change value at a
register, program will hang in an infinite loop (yes, all code below this block
is unreachable code, and compiler such as Microsoft Visual C++ knows about it).
Also this loop was optimized in equivalent program with the same infinite loop,
but without involving variable initialization and fetching. 'jmp label' means t
he same as 'goto label' in C code.
Second block have volatile qualifier and have more complex assembler output (initial
izing 'flag' with 'mov' instruction, in a loop fetching this flag into CPU regis
ter 'eax' with a 'mov' instruction, comparing fetched value with zero with 'test
' instruction, and returning to the beginning of the loop if 'flag' was not equa
l to zero. 'jne' means 'goto if not equal'). This is all because volatile keywor
d prohibits compiler to cache variable value into CPU register, and it is fetche
d in all loop iterations. Such code is not always is an infinite loop, because a
nother thread in the same program potentially could change value of variable 'fl
ag' and first thread will exit the loop.
It is important to understand that volatile keyword is just a directive for compiler
and it works only at a compile-time. For example, the fact of using interlocked
operation differs from just a compiler option, since special assembler commands
are produced. Thus, interlocked instructions are most like to hardware directiv
es, and they work at a run-time.
A volatile qualifier must be used when reading the contents of a memory location
whose value can change unknown to the current program.
A volatile qualifier must be used for shared data modified in signal handlers or
interrupt service routines.
After const, we treat volatile. The reason for having this type qualifier is mai
nly to do with the problems that are encountered in real-time or embedded system
s programming using C. Imagine that you are writing code that controls a hardwar
e device by placing appropriate values in hardware registers at known absolute a
ddresses.
Let's imagine that the device has two registers, each 16 bits long, at ascending
memory addresses; the first one is the control and status register (csr) and th
e second is a data port. The traditional way of accessing such a device is like
this:
/* Standard C example but without const or volatile */
/*
* Declare the device registers
* Whether to use int or short
* is implementation dependent
*/
struct devregs{
unsigned short csr;
unsigned short data;
};
/* bit patterns
#define ERROR
#define READY
#define RESET
in the csr */
0x1
0x2
0x4
struct devregs{
unsigned short volatile csr;
unsigned short const volatile data;
};
/* bit patterns
#define ERROR
#define READY
#define RESET
in the csr */
0x1
0x2
0x4
hrough the pointer. Our feeling is that, although this would work, it is bad sty
le. The volatile declaration belongs in the structure: it is the device register
s which are volatile and that is where the information should be kept; it reinfo
rces the fact for a human reader.
So, for any object likely to be subject to modification either by hardware or as
ynchronous interrupt service routines, the volatile type qualifier is important.
Now, just when you thought that you understood all that, here comes the final tw
ist. A declaration like this:
volatile struct devregs{
/* stuff */
}v_decl;
declares the type struct devregs and also a volatile-qualified object of that ty
pe, called v_decl. A later declaration like this
struct devregs nv_decl;
declares nv_decl which is not qualified with volatile! The qualification is not
part of the type of struct devregs but applies only to the declaration of v_decl
. Look at it this way round, which perhaps makes the situation more clear (the t
wo declarations are the same in their effect):
struct devregs{
/* stuff */
}volatile v_decl;
If you do want to get a shorthand way of attaching a qualifier to another type,
you can use typedef to do it:
struct x{
int a;
};
typedef const struct x csx;
csx const_sx;
struct x non_const_sx = {1};
const_sx = non_const_sx;