1. 19
  1. 8

    Volatile reads and writes should have been function calls that take pointers. It would have been a straightforward interface that doesn’t raise all the questions that implicit magic of volatile types does.

    1. 4

      In retrospect, I agree, but with a minimally optimising compiler, on a single-core system with no caches (i.e. the original C deployment scenario), volatile is easy to understand: the compiler may not elide loads or stores to those variables. It wasn’t until C11 that C got a memory model that even allowed you to write async-signal safe using standard C (volatile reads and writes can be reordered with respect to other operations, including volatile reads and writes of other objects), let alone threading.

      The rule of thumb for volatile is: if you’re using it for anything other than memory-mapped I/O, you’re doing it wrong.

      1. 3

        Rules of thumb are hard to make all-encompassing. You also need volatile when you reading and writing variables between the main program and a signal handler. Otherwise your compiler might optimize while (!g); into a single read, resulting in an infinite loop if your program hasn’t gotten the signal yet.

        1. 1

          You also need volatile when you reading and writing variables between the main program and a signal handler

          This is an example of incorrect use of volatile (though the closest portable approximation of something that would mostly work that was possible prior to C11). The compiler may not elide the loads or stores to the volatile variable, but it is completely allowed to reorder them with respect to other loads and stores and so your program may have an observed execution order that is very surprising to someone reading the source. For this purpose, you want an _Atomic-qualified variable, with the correct memory order to inhibit whichever kind of move you want. The best option if the variable isn’t shared across threads is a relaxed-consistency atomic op combined with an atomic signal fence.