9

I am a embedded developer and use volatile keyword when working with I/O ports. But my Project manager suggested using volatile keyword is harmful and has lot of draw backs, But i find in most of the cases volatile is useful in embedded programming, As per my knowledge volatile is harmful in kernel code as the changes to our code will become unpredictable. There are any drawbacks using volatile in Embedded Systems also?

arahan sharma
  • 79
  • 1
  • 3
  • 6
    Are you seriously listen to programming advice given by a *manager*? – KBart Jan 05 '15 at 15:22
  • 2
    The reason you don't use `volatile` in the kernel is because you don't need to. Accessing I/O ports is done through wrapper functions in the kernel. – tangrs Jan 08 '15 at 01:02
  • The phrase 'volatile considered harmful' is a joke; just like the manager pattern (that is another joke). The jux of the paper is to say that volatile doesn't solve many issues. Especially as it relates to SMP systems, it is not a solution. I believe [this is the source](https://www.kernel.org/doc/Documentation/process/volatile-considered-harmful.rst). It does not say **NOT** to use `volatile`. Taking the title literally is the error. Search 'goto considered harmful' for the origin of the phrase. dijkstra is the real deal and the rest of us are pretenders. – artless noise Dec 20 '22 at 01:23

5 Answers5

27

No, volatile is not harmful. In any situation. Ever. There is no possible well-formed piece of code that will break with the addition of volatile to an object (and pointers to that object). However, volatile is often poorly understood. The reason the kernel docs state that volatile is to be considered harmful is that people kept using it for synchronization between kernel threads in broken ways. In particular, they used volatile integer variables as though access to them was guaranteed to be atomic, which it isn't.

volatile is also not useless, and particularly if you go bare-metal, you will need it. But, like any other tool, it is important to understand the semantics of volatile before using it.

What volatile is

Access to volatile objects is, in the standard, considered a side-effect in the same way as incrementing or decrementing by ++ and --. In particular, this means that 5.1.2.3 (3), which says

(...) An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced (including any caused by calling a function or accessing a volatile object)

does not apply. The compiler has to chuck out everything it thinks it knows about the value of a volatile variable at every sequence point. (like other side-effects, when access to volatile objects happens is governed by sequence points)

The effect of this is largely the prohibition of certain optimizations. Take, for example, the code

int i;

void foo(void) {
  i = 0;
  while(i == 0) {
    // do stuff that does not touch i
  }
}

The compiler is allowed to make this into an infinite loop that never checks i again because it can deduce that the value of i is not changed in the loop, and thus that i == 0 will never be false. This holds true even if there is another thread or an interrupt handler that could conceivably change i. The compiler does not know about them, and it does not care. It is explicitly allowed to not care.

Contrast this with

int volatile i;

void foo(void) {
  i = 0;
  while(i == 0) { // Note: This is still broken, only a little less so.
    // do stuff that does not touch i
  }
}

Now the compiler has to assume that i can change at any time and cannot do this optimization. This means, of course, that if you deal with interrupt handlers and threads, volatile objects are necessary for synchronisation. They are not, however, sufficient.

What volatile isn't

What volatile does not guarantee is atomic access. This should make intuitive sense if you're used to embedded programming. Consider, if you will, the following piece of code for an 8-bit AVR MCU:

uint32_t volatile i;

ISR(TIMER0_OVF_vect) {
  ++i;
}

void some_function_in_the_main_loop(void) {
  for(;;) {
    do_something_with(i); // This is thoroughly broken.
  }
}

The reason this code is broken is that access to i is not atomic -- cannot be atomic on an 8-bit MCU. In this simple case, for example, the following might happen:

  • i is 0x0000ffff
  • do_something_with(i) is about to be called
  • the high two bytes of i are copied into the parameter slot for this call
  • at this point, timer 0 overflows and the main loop is interrupted
  • the ISR changes i. The lower two bytes of i overflow and are now 0. i is now 0x00010000.
  • the main loop continues, and the lower two bytes of i are copied into the parameter slot
  • do_something_with is called with 0 as its parameter.

Similar things can happen on PCs and other platforms. If anything, more opportunities it can fail open up with a more complex architecture.

Takeaway

So no, using volatile is not bad, and you will (often) have to do it in bare-metal code. However, when you do use it, you have to keep in mind that it is not a magic wand, and that you will still have to make sure you don't trip over yourself. In embedded code, there's often a platform-specific way to handle the problem of atomicity; in the case of AVR, for example, the usual crowbar method is to disable interrupts for the duration, as in

uint32_t x;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
  x = i;
}
do_something_with(x);

...where the ATOMIC_BLOCK macro calls cli() (disable interrupts) before and sei() (enable interrupts) afterwards if they were enabled beforehand.

With C11, which is the first C standard that explicitly acknowledges the existence of multithreading, a new family of atomic types and memory fencing operations have been introduced that can be used for inter-thread synchronisation and in many cases make use of volatile unnecessary. If you can use those, do it, but it'll likely be some time before they reach all common embedded toolchains. With them, the loop above could be fixed like this:

atomic_int i;

void foo(void) {
  atomic_store(&i, 0);
  while(atomic_load(&i) == 0) {
    // do stuff that does not touch i
  }
}

...in its most basic form. The precise semantics of the more relaxed memory order semantics go way beyond the scope of a SO answer, so I'll stick with the default sequentially consistent stuff here.

If you're interested in it, Gil Hamilton provided a link in the comments to an explanation of a lock-free stack implementation using C11 atomics, although I don't feel it's a terribly good write-up of the memory order semantics themselves. The C11 model does, however, appear to closely mirror the C++11 memory model, of which a useful presentation exists here. If I find a link to a C11-specific write-up, I will put it here later.

Wintermute
  • 42,983
  • 5
  • 77
  • 80
  • 3
    There is more than atomicity to consider. You also need to be wary of caching effects in multi-threaded code. In the last example, sig_atomic_t alone is not sufficient; you may also need memory barriers on some architectures. Consider if i is in the local CPU cache initially and has value 0 but then a thread running on another CPU changes it to 1. Depending on the processor cache coherency model and the code in the other thread, it's possible your locally cached data might never get re-read from main memory. So even though the compiler is still executing loads, you might loop forever. – Gil Hamilton Jan 05 '15 at 12:03
  • Is that not prohibited by 5.1.2.4 (22) in C11? – Wintermute Jan 05 '15 at 12:12
  • 1
    No. First, sig_atomic_t was not new for C11 and is not included in the new atomic operations; it only provides protections w.r.t. to signal handlers. Second, if you want the new atomic semantics, you must use the atomic_XXX() macros defined in . See this intro, for example: http://nullprogram.com/blog/2014/09/02/ – Gil Hamilton Jan 05 '15 at 12:39
  • That link does not exactly feel like an introduction, but if I read it correctly, the C11 memory model is very nearly the same as the one in C++11, for which good presentations exist. I updated the last part of the answer to reflect all this. The `sig_atomic_t` thing surprised me a bit, but I suppose it makes sense that it isn't atomic in the context of fences. – Wintermute Jan 05 '15 at 13:55
  • 1
    You *do* realize that the impediment to optimization caused by gratuitous use of `volatile` constitutes harm, right? – SamB Aug 30 '15 at 21:49
  • No, volatile is not harmful. **In any situation**. It is true, it will not 'break' any code ever. However, if the code is already b0rken, it does not necessarily fix it. Often it can appear to fix it (due altering timing) for some test cases but not all. Later in the intro, this is alluded to, but I think the **In any situation** should be removed as you later state a case. I think you were more of the mind as in a coding standard (no situation to ban it). – artless noise Jan 07 '23 at 17:55
5

volatile is only useful when the so qualified object can change asynchronously. Such changes can happen

  • if the object is in fact an hardware IO register or similar that has changes external to your program
  • if the object might be changed by a signal handler
  • if the object is changed between calls to setjmp and longjmp

in all these cases you must declare your object volatile, otherwise your program will not work correctly. (And you might notice that objects shared between different threads is not in the list.)

In all other cases you shouldn't, because you may be missing optimization opportunities. On the other hand, qualifying an object volatile that doesn't fall under the points above will not make your code incorrect.

Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177
2

Not using volatile where necessary and appropriate is far more likely to be harmful! The solution to any perceived problems with volatile is not to ban its use, because there are a number of cases where it is necessary for safe and correct semantics. Rather the solution is to understand its purpose and its behaviour.

It is essential for any data that may be changed outside of the knowledge of the compiler, such as I/O and dual-ported or DMA memory. It is also necessary for access to memory shared between execution contexts such as threads and interrupt-handlers; this is where perhaps the confusion lies; it ensures an explicit read of such memory, and does not enforce atomicity or mutual exclusion - additional mechanisms are required for that, but that does not preclude volatile, but it is merely part of the solution to shared memory access.

See the following articles of the use of volatile (and send them to your project manager too!):

Clifford
  • 88,407
  • 13
  • 85
  • 165
1

Volatile tells the compiler not to optimize anything that has to do with the volatile variable.

Why the "volatile" type class should not be used? - Best article in Kernel doc

https://www.kernel.org/doc/Documentation/volatile-considered-harmful.txt

Jeegar Patel
  • 26,264
  • 51
  • 149
  • 222
0

volatile is a keyword in c which tell the compiler not to do any kind of optimization on that variable.

Let me give you a simple example:

int temp;
for ( i=0 ;i <5 ; i++ )
{
    temp = 5;

}

what compiler will do to make the code optimized :

int temp;
temp = 5; /* assigned temp variable before the loop. */
for ( i=0 ;i <5 ; i++ )
{

}

But if we mention volatile keyword then compiler will not do any kind of optimization in temp variable.

volatile int temp;
for ( i=0 ;i <5 ; i++ )
{
    temp = 5;

}

"Volatile Considered Harmful" ---> I don't consider volatile as harmful. You use volatile where you don't want any kind of optimization from compiler end.

For example consider this piece of code is used by a thermometer company and temp is a variable used to take the temperature of the atmosphere which can change anytime. So if we do not use volatile then compiler will do the optimization and the atmosphere temperature will always be same.

rabi shaw
  • 441
  • 1
  • 3
  • 14