3

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

Qwertiy
  • 19,681
  • 15
  • 61
  • 128

2 Answers2

8

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.

Grzegorz Szpetkowski
  • 36,988
  • 6
  • 90
  • 137
  • Like my answer says, it's invalid to modify a `const` value - however, it is NOT invalid to remove `volatile` - it will just make the compiler potentially cache global values. – Mats Petersson Jul 20 '15 at 22:49
  • @MatsPetersson: Hopefully my answer is more clear now. Yes, you are right, you can drop `volatile` qualifier and it's not UB to do that. – Grzegorz Szpetkowski Jul 20 '15 at 22:52
  • And the fact that constant is declared as volatile doesn't affect this statement? – Qwertiy Jul 20 '15 at 23:13
  • @Qwertiy: It does not affect, because `const` already provoked an UB. – Grzegorz Szpetkowski Jul 20 '15 at 23:55
  • "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." What kinds of actual bad/surprising behaviors could happen, beyond the loss of the optimization-defeating properties of `volatile`? – Kaz Sep 22 '15 at 22:56
  • @Kaz: It means that particular language standard imposes no requirements, so theoretically _anything_ could happen. It may mean loss of data or even OS/hardware failure on some specific implementations, though it sound hardly unlikely. For modern implementations it may result into optimization drawbacks, but still you have no guarantee how to it would work (or not) on an other compiler. – Grzegorz Szpetkowski Sep 24 '15 at 15:31
  • What if it contradicts other requirements? Suppose ISO C or C++ suddenly were to acquire a clause which says "whenever two values of type `int` are added together whose mathematical sum is 42, the behavior is undefined". Although this explicitly says that no requirement applies in that case, the remaining body of the document continues to give a requirement if we just ignore that conflicting sentence. In this case, if we just remove that sentence, in fact the behavior can be deduced; all that is making it undefined is that sentence. – Kaz Sep 24 '15 at 19:27
  • Okay, so a better question is perhaps: what useful feature can an implementation provide, which will break for programs which access the `volatile` object as non-`volatile`? (I.e. which is enabled by that statement in the standard?) What can a compiler do based on knowing that an object is declared `volatile`, which will break if something elsewhere accesses that object otherwise? – Kaz Sep 24 '15 at 19:43
  • Sorry about the extended discussion, Grzegorz. I opened a question about this on the Programmers SE: http://programmers.stackexchange.com/questions/298193/benefit-of-non-volatile-access-to-volatile-objects-being-undefined (I provide one hypothetical benefit in the question, to show some effort. ) – Kaz Sep 24 '15 at 20:28
5

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]

Mats Petersson
  • 126,704
  • 14
  • 140
  • 227
  • What do you mean by thread? If I place the same code into the other thread and wait until it finishes, is will the code become correct? Or you've meant the process? – Qwertiy Jul 20 '15 at 23:12
  • 1
    What do you mean by remove. If you mean point to then it is undefined as well. *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.* – this Jul 20 '15 at 23:17
  • The part on `const` is correct, but your part about `volatile` looks wrong. C11 says *"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."* – Jens Gustedt Jul 20 '15 at 23:39
  • @Qwertiy: See edit 2. Any value that is updated by thread, process or external hardware, outside of the "current code" will need to use `volatile` for the value to be correctly re-read (or written in case of non-`const` variables). – Mats Petersson Jul 20 '15 at 23:42
  • @MatsPetersson, I still find what you write very confusing. For both `const` and `volatile` you can't simply remove such a qualifier. There are subtle differences, thought, and you'd have to be very careful here, because this also is a C and C++ question, and I think there are even subtle differences between them. – Jens Gustedt Jul 20 '15 at 23:44
  • @JensGustedt: Thinking about it and looking at other answers in similar vein, it would appear that pre-C11 didn't state clearly what is and isn't allowed with casting away `volatile`, but C11 and C++11 both state that it's undefined. Of course, within undefined behaviour is the possibility of "it still works as expected". – Mats Petersson Jul 20 '15 at 23:50
  • @MatsPetersson: A major weakness of the Standard IMHO is that there is no behavioral category for things where *most* implementations will be able to specify a specific behavior, but some may be unable to define anything more specific than "unconstrained arbitrary behavior". From a purely requirements-driven standpoint, such a category would be essentially equivalent to UB, but from a normative standpoint it would be very different. IMHO, most things which are categorized as UB should have been characterized as "optionally-defined behaviors", with the implication that implementations... – supercat Jul 22 '15 at 22:31
  • ...should attempt to define them usefully even if they are not required to do so. The semantics of volatile variables, as well as reads and writes thereof, are in general Implementation-Defined; an implementation spec would generally define the relationship among different types of memory reads even though the C spec does not. – supercat Jul 22 '15 at 22:33