Qwertiy
Qwertiy

Reputation: 21400

Can indirect change of volatile const be treated as undefined behavior?

Does volatile write to volatile const introduce undefined behavior? What if I drop volatile when writing?

volatile const int x = 42;
const volatile int *p = &x;
*(volatile int *)p = 8; // Does this line introduce undefined behavior?
*(int *)p = 16; // And what about this one?

Compilable code

Upvotes: 3

Views: 293

Answers (2)

Grzegorz Szpetkowski
Grzegorz Szpetkowski

Reputation: 37934

It is undefined behavior (for both statements) as you attempt to modify the "initial" const object. From C11 (N1570) 6.7.3/p6 Type qualifiers (emphasis mine):

If an attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type, the behavior is undefined.

For completeness it may be worth adding, that Standard says also that:

If an attempt is made to refer to an object defined with a volatile-qualified type through use of an lvalue with non-volatile-qualified type, the behavior is undefined.

Hence latter statement, that is:

*(int *)p = 16;

is undefined for second phrase as well (it's a "double UB").

I believe that rules are the same for C++, but don't own copy of C++14 to confirm.

Upvotes: 8

Mats Petersson
Mats Petersson

Reputation: 129374

Writing to a variable that is originally const is undefined behaviour, so all your example writes to *p are undefined.

Removing volatile in itself is not undefined in and of itself.

However, if we have something like const volatile int *p = (const volatile int*)0x12340000; /* Address of hw register */, then removing volatile may cause the hardware to update the register value, but your program doesn't pick it up. (E.g. if we "busy wait" with while(*p & 0x01) ;, the compiler should reload the value pointed to by p every time, but while((*(const int *)p) & 1) ;, the compiler is entirely free to read the value ones, and re-use the initial value to loop forever if bit 0 is set).

You could of course have extern volatie int x; and then use const volatile int *p = &x; in some code, and x gets updated by some other piece of code outside of your current translation unit (e.g. another thread) - in which case removing either const or volatile is valid, but as above, you may "miss" updated values because the compiler doesn't expect the global value to get updated outside of your module unless you call functions. [Edit, no, taking away volatile by casting the value is also forbidden in the standard - it is however valid to add const or volatile to something, and then remove it again if it the original object referred to did not have it].

Edti2: volatile is needed to tell the compiler that "the value may change at any time, even if you think nothing should have changed it". This happens, in general, in two situations:

  1. Hardware registers that are updated outside of the software altogether - such as status registers for a serial port, a timer-counter register, or interrupt status of the interrupt controller, to name a few case - there are thousands of other variations, but it's all the same idea: The hardware changes the value, without direct relation to the software that is accessing such registers.
  2. Variabled updated by another thread within the process (or in case of shared memory, by another process) - again, the compiler won't be able to "see" such changes. Often, one can write code that appear to function correctly by calling a function inside wait-loops and such, and the compiler will the reload values of non-local variables anyway, but some time later the compiler decides to inline that function call, and then realizes that the code is not updating that value, so doesn't reload the value -> bug when you inspect the "updated" value and find the same old value that the compiler had already loaded earlier.

Note also that volatile is NOT a guarantee for any kind of thread/process correctness - it will just guarantee that the compiler doesn't skip over reads or writes to that variable. It is still up to the programmer to ensure that for example multiple values that are dependent on ordering are indeed updated in the correct order, and on systems where caches are not coherent between processing units, that the caches are made coherent via the software [for example a CPU and a GPU may not use coherent memory updates, so a write by the CPU does not reach the GPU until the cache has been flushed on the CPU - no amount of applying volatile to the code will fix this]

Upvotes: 5

Related Questions