ProgrammingEmbedded/IoT Developer

What are the subtleties of using the volatile keyword in C, and what mistakes occur with its improper use, especially when dealing with multithreading and hardware registers?

Pass interviews with Hintsage AI assistant

Answer.

The volatile keyword tells the compiler that a variable may change unexpectedly (for example, by hardware, another thread, or an interrupt handler), and prevents it from caching its value or optimizing accesses to it.

Used for:

  • Working with hardware registers.
  • For variables changed in interrupt handlers.
  • For variables used in inter-thread communication (but importantly: volatile does not guarantee atomicity or synchronization!).

Example usage:

volatile int flag = 0; void handler() { flag = 1; // interrupt handler } void loop() { while (!flag) { // waiting for an event } // ... }

Without volatile, the compiler might replace the loop with an infinite one (not reading flag from memory), with volatile, the variable is read from memory each time.


Trick question.

Is it enough to use volatile for correct information exchange between threads?

A common mistake is to assume that volatile provides memory synchronization between threads and makes operations atomic.

Correct answer:

volatile does not protect against data races in a multithreaded environment and does not provide memory barriers: it merely tells the compiler not to optimize the access. For guaranteed correctness, synchronization primitives (mutex, atomic operations, etc.) must be used.

// This is unsafe! volatile int ready = 0; void thread1() { data = 123; // write data ready = 1; // signal the other thread } void thread2() { while (!ready); // waiting for the event printf("data = %d\n", data); // data may not have updated yet! }

History


In a microcontroller management project, the variable for communication between the main loop and the interrupt handler was not defined as volatile, causing the firmware to occasionally hang — changes to the flag went unnoticed.


In a multithreaded server, volatile was used for signaling, believing it was sufficient, only to encounter elusive bugs: sometimes a thread read outdated data or was in an inconsistent state — an atomic variable or mutex was necessary.


Error when working with hardware registers: values were written/read without volatile, and the compiler optimized the access, leading to complete disregard for new register values — some commands did not work.