“I do not think that word…

…means what you think it means.”
— Inigo Montoya, The Princess Bride

In C and C++ (indeed, most computer languages), there are keywords that mean very specific things to the compiler, and of course the programmer — but the two meanings are not necessarily identical!

This page describes common C/C++ keywords that have explicit meanings, but the (programmer’s) common view of those keywords may be subtly different. Fully understanding how the compiler treats the keywords may prevent errors in your code.

Contents

const

This is an easy one! const means that the value never changes, right?

Actually, no. When applied to a variable, it means that you can never change the variable: your code is not allowed to assign a value to it. Something, somewhere else, may very well change the value.

Example

There are numerous circumstances where const is used on a changeable object. The most common is when it is passed as const. In that scenario, the “variable” is assumed to hold the same value for the duration of the call (which can be a poor assumption). But the important thing is that the compiler enforces that the code in the function cannot write to the object.

Actuality

In a simple program, on a simple system, it is (probably) true that a variable defined as const never changes. However, in more complex systems a value might change “underneath” the code. For instance:

  • An interrupt could change a value.
    A careful programmer might declare a variable as const to prevent normal code that can access it from confusing an interrupt — but the interrupt could definitely change the value.
  • Another thread could change the value.
    Similar to the interrupt case above, a careful programmer might declare read-only access to a variable that another function running in another thread will change.
More uses

And in C++, there are other uses of const. One example is declaring that a member function doesn’t modify the class, so can be safely called on a const object:

class String {
public: // Functions
   String();
   String(const char *string);
   inline unsigned Length() const;
private: // Variables
   const char *string;
   unsigned length;
}; // String

Length() promises not to modify the class, so can be called on a const String. Any attempt by the code to modify a member variable will be flagged by the compiler as an error — unless that member is mutable. So a const function can still modify (specific) members of a class: it’s not as const as you thought it was!

Takeaway

const doesn’t mean the value won’t change; it merely means that you can’t change it.

volatile

This is an esoteric keyword that many C/C++ programmers don’t know about — but when they learn about it, they think it does way more than it actually does!

Example

Here is some code that, on the face of it, looks ridiculous:

char key;
char WaitForKey() {
   char temp;
   do {
      temp = key;
   } while (temp == '\0');
   key = '\0';
   return temp;
} // WaitForKey()

This code keeps accessing the key variable as long as it’s 0. The only problem is, on the face of it, key will always be 0, or not. This is either an infinite loop, or a useless one!

What isn’t obvious is that key can be modified by a keyboard interrupt. When a key on the keyboard is pressed, the interrupt will read the keypress and modify the key variable. That will change temp, and the function will exit with the value of the pressed key.

Intended usage

Perfect, right? Sorry, no. The programmer is not the only thing that can be confused by the above code. The compiler is allowed to use the same uninformed logic, and test key once then decide from that test what to do from there: loop forever or not loop at all.

key is declared as a normal variable, and so the compiler can make the assumption that, unless that code modifies the variable, the variable’s value won’t change. Since that’s not true (the interrupt can also modify it), the compiler needs to be told:

volatile char key;

Declaring a variable as volatile does two things:

  1. It forces the compiler to explicitly re-read the variable every time it is used, rather than “deducing” its value from previous reads;
  2. It forces the compiler to explicitly write back any change to the variable as quickly as possible, rather than holding off in case it’s never changed again anyway.

Assumptions

When a programmer reads the above features of volatile, they often think “Hey! That’s basically what a shared variable can suffer from. If I declare my variable as volatile, then multiple threads can access the variable simultaneously and not cause contention!”

Only, no. The main problem is that while multiple (software) threads wouldn’t get confused with a volatile variable, nobody told a multi-processor (hardware) architecture. Each processor (probably) has its own cache, and the code needs to explicitly tell the processor to update the cache — volatile isn’t defined to do that.

Actually, it might work: for a “strong” memory model architecture like Intel/AMD’s x86/x64 architecture, cache coherency will work in the programmer’s favour. Unfortunately, ARM (for example) only has a weak memory model. You cannot rely on volatile to maintain inter-thread coherency — that’s not what it means!

Takeaway

All that volatile means is that the compiler shouldn’t “remember” the contents of a variable for optimisation purposes — and that it should write any new value back as quickly as possible. There is no extra meaning with respect to multi-threading coherency: you need an explicit construct to do that, such as C++’s std::atomic<>.