liveBook · Manning

Volatile qualification

As explained in Modern C, Third Edition

The C volatile qualifier specifies that accesses to an object may have externally observable effects and must not be treated as freely eliminable or cacheable by the compiler.

Keeping a measurement loop

An empty `for` loop has no effect and may disappear during optimization. Declaring its counter `uint64_t volatile i` keeps accesses to the counter in the generated code. Alternatively, an ordinary counter can assign to a volatile object once per iteration. These choices do not cost the same: the measured loop with a volatile store was about 80% faster than the one with a volatile counter. section 16.4

The qualifier `volatile` applies syntactically like `const` and `restrict`. Each access to a volatile object reloads its stored value, and each modification stores it. This blocks optimizations that assume the value cannot change unexpectedly, but it can also make code slower. TAKEAWAY: Qualify an object only when its access behavior needs this protection. section 19.5

A value changed before a long jump

A local `depth` value used after `setjmp` may be changed on a path that exits through `longjmp`. An optimizer cannot safely infer its value there from the normal return path. Declaring it `unsigned volatile depth = 0;` ensures subsequent accesses use the stored value; code that receives a pointer to `depth` must be amended accordingly. section 19.5

A signal handler creates a related problem: the compiler cannot predict when it will run, so objects it may change need volatile qualification. Volatile alone does not make an operation uninterruptible. A signal can arrive partway through a multi-instruction write and expose a partially updated value. To pass information between a handler and ordinary execution, use `volatile sig_atomic_t`, `atomic_flag`, or another lock-free atomic type; keep the handler's work small. section 19.6 section 19.7

Listing 16.7 Instrumenting three for loops with struct timespec
    timespec_get(&t[0], TIME_UTC);
    /* Volatile for i ensures that the loop is effected */
    for (uint64_t volatile i = 0; i < iterations; ++i) {
      /* do nothing */
    }
    timespec_get(&t[1], TIME_UTC);
    /* s must be volatile to ensure that the loop is effected */
    for (uint64_t i = 0; i < iterations; ++i) {
      s = i;
    }
    timespec_get(&t[2], TIME_UTC);
    /* Opaque computation ensures that the loop is effected */
    for (uint64_t i = 1; accu0 < upper; i += 2) {
      accu0 += i;
    }
    timespec_get(&t[3], TIME_UTC);
    

Compare the empty loop with a volatile counter, the loop with a volatile store, and the loop using an opaque computation. section 16.4

How Modern C, Third Edition covers it

How deep it goes
Explains reloads and stores, then traces optimization consequences through assembly and exceptional control flow; distinguishes volatile access from uninterruptible operations.
Examples
Compares timed C loops and GCC and Clang assembly, then uses a volatile depth variable in a setjmp and longjmp example.
What it assumes
Assumes familiarity with C loops and qualifiers such as const and restrict; explains volatile access behavior through examples.
What you can do after
Diagnose/debug with it when optimization discards measurement work or makes assumptions about values changed across unusual control flow.
How it connects
Connects volatile qualification to compiler optimization, performance measurement, setjmp and longjmp, signal handlers, and lock-free atomic types.

Also explained in

Compare how 2 books explain volatile qualification →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Modern C, Third Edition. The section links point to the parts of the book it draws on.