5

This is a question about ISO C, which contains this sentence:

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.

In ISO9899:1999 (C99) this appears in 6.7.3 Type Qualifiers.

Firstly, if we entirely removed this sentence from the text, would the behavior still be undefined? That is to say, is this re-stating an existing lack of a requirement, or is it explicitly taking away a requirement that could otherwise be inferred from elsewhere in the text? (Imagine that a sentence were added which says "whenever two values of type int are added together whose arithmetic sum is forty-two, the behavior is undefined". Without such a sentence, the requirement for such an addition to produce a value can (and is) inferred).

Secondly, what useful behaviors does this allow an implementation to provide? That is to say, can an implementation do something beneficial which breaks programs that access something volatile with a non-volatile lvalue, and which therefore depends on the above sentence? Are there any implementations which do this, whatever it is?

For example, it is interesting and noteworthy that C programs can access const-qualified objects with non-const lvalues, just not store to them. Making the store behavior undefined is tangibly beneficial: implementations can place constants in read-only storage, and in some cases can propagate constants through a program image at translation time as if they were manifest constants. Yet, mere non-const access is required to work. Not requiring non-const accesses to const objects to work would presumably be a permissiveness which brings no benefits. Yet, in the case of volatile, it is that strict: no non-volatile-qualified access is required to work. What is the benefit?

Can an object which is defined as volatile by the C program itself be endowed with useful properties which break if there take place any accesses whatsoever to that object through a non-volatile lvalue?

(Under "useful", I would specifically like to exclude diagnosis. Terminating the program upon detecting such an access, without a message, or with a message like "volatile accessed as non-volatile" doesn't count as useful; it serves only to enforce the lack of requirement.)

(Also, I'm not interested in memory mapped registers and such; strictly objects defined by the program in static, dynamic or automatic storage. Use of hardware registers is inherently nonportable.)

All I can think of is that a non-volatile access to a volatile object could retrieve a stale value, rather than value that was last stored (properly, through the volatile lvalue). This is an economic benefit in the following way: if a write occurs through, say, a volatile int lvalue, the compiler does not have to suspect that this has any effect on any plain int lvalue. This leads to better code generation in functions which work with mixtures volatile and non-volatile objects of the same type:

volatile int global;

void foo(int *p)
{
   int x = *p, y;
   global++; 
   y = *p;
}

Here, the compiler doesn't have to consider that *p might be an alias for global, because that usage is not required to work according to ISO C. Hence it can freely assume that the value of *p does not change between the two references. Both x and y receive to the same value, derived from a single access to *p.

If the aliasing were permitted, then the compiler would have to generate code to re-load *p on the second access, in order that it pick up the most recent value stored in global.

(If we remove volatile from the above program fragment, then this is in fact the case; it has to be at least suspected, if not confirmed. that *p may be an alias for global and so *p has to be re-inspected. Or else code has to be generated which compares the run-time value of p to the address of global and takes two different paths.)

However, we don't introduce volatile into programs to obtain this kind of obscure optimization benefit; it is used to defeat optimization. The restrict keyword can be used instead to allow the implementation to reason about lvalues not being aliased. It seems that the above cannot be the rationale.

6 Answers 6

1

The volatile keyword means a lot of different things on a lot of different compilers, but one of their more important uses has been to represent special memory-mapped hardware. So declaring the behavior in that respect undefined allows compilers to keep using it to represent that hardware. Same reason right-shift, divide and modulus of a negative signed integer is not specified exactly: that lets each CPU’s native instruction set work. If you want exactly-defined behavior, there are atomics and div().

4
  • Programs which use memory-mapped hardware invoke undefined-behavior pretty much no matter how they gain access to it. I'm only interested in the portable C uses. volatile is required for C declared objects in two cases: objects that are modified since state is recorded with setjmp, but before the state is restored with longjmp, whose values are then used upon restoration. Secondly, asynchronous signal handlers may only modify volatile sig_atomic_t objects. I'm only interested in the portable C semantics.
    – Kaz
    Sep 24, 2015 at 22:35
  • Right. And part of the motive for declaring this behavior undefined is probably so this keyword can keep being used to declare those implementation-specific behaviors.
    – Davislor
    Sep 24, 2015 at 22:41
  • Those aren't implementation-specific behaviors. The volatile uses with setjmp and signal handlers are required by ISO C. volatile defeats the optimizations which could cause a restored setjmp context to restore stale register-cached values of locals. It also defeats the optimizations that could cause accesses to a global sig_atomic_t variable to ignore updates by a signal handler.
    – Kaz
    Sep 24, 2015 at 23:25
  • What volatile does is quite clear. Objects which are volatile have to have values (at sequence points) which agree with the abstract semantics. Also, a footnote says that in an implementation in which objects, at sequence points, have the values which agree with the abstract semantics (i.e. a not very well optimized implementation), then volatile is redundant.
    – Kaz
    Sep 24, 2015 at 23:26
1

If you are only interested in "portable C," then you will find the keyword volatile to serve little to no use, and thus the requirements will seem unusual. It's entire purpose in the language is to permit compilers to expose memory mapped hardware. It gets some minimal life if you add a [non-portable] threading library into the mix, but at its core, it's job is to do implementation specific behaviors. So much so that the spec states:

An object that has volatile-qualified type may be modified in ways unknown to the implementation or have other unknown side effects. ... What constitutes an access to an object that has volatile-qualified type is implementation-defined. (ISO/IEC 9899:TC2 6.7.3.6)

The value in denying access to a volatile object via non-volatile means becomes apparent in that line. The spec permits "volatile" operations to be implemented differently than "non-volatile" operations. As a trivial example, a volatile write might look up a mapped register in a mapping, and then write to the register instead, while the non-volatile write simply writes to that memory. That example might be a little unrealistic, but the developers of C went to great lengths to support writing software in C on many platforms, and other than adding one line into the spec (the one you mentioned), it literally cost them nothing to add this support to the language. In doing so, all sorts of exotic memory mapping situations are made much simpler.

On modern x86 machines, there's probably no reason for the compiler to take advantage of this. In fact, I would not be surprised if the x86 compilers "undefined behavior" in this case is to just do what you think should have been done. However, on more anemic platforms, the ability to not have to worry about the case where a developer access a volatile via a non-volatile lvalue may be a substantial difference in compiler code generation.

6
  • Some compiler writers don't need a particular "reason" to take advantage of Undefined Behavior. If they can determine that the Standard imposes no requirements on the behavior of a certain code path, they'll assume it cannot occur, regardless of whether there would otherwise be any real difficulty executing the code on that path.
    – supercat
    Jan 29, 2016 at 23:56
  • @supercat Yes, when the specification does not impose any requirements on something, compiler writers can do what they want....
    – Cort Ammon
    Jan 30, 2016 at 0:15
  • Unfortunately, the authors of the Standard are often extremely loath to say what compilers "should" do, with the consequence that even in cases where programmers and compiler writers would historically have agreed that certain behaviors should be defined when practical, and where the vast majority compilers for non-weird platforms have historically defined them absolutely consistently, some modern compiler writers feel no obligation to abide by such behaviors.
    – supercat
    Jan 30, 2016 at 0:18
  • @supercat I suppose I could put a disclaimer on every answer that I write which you comment on, that if you're willing to rely on compiler specific behaviors, you can get away with things which the spec does not always allow you to do. However, generally speaking, I assume that people who ask questions about the spec are actually interested in answers about the spec, rather than what one might wish the spec had been, had they been in charge. After all, we are all free to write our own language to respond to C's apparent deficiencies. If its better than C, it may even take off.
    – Cort Ammon
    Jan 30, 2016 at 0:30
  • The problem isn't with the language that people actually use--it's with the failure of the standard to acknowledge reality. If some particular platform has a weird hardware reason why a non-qualified access to a volatile variable should have some consequence beyond being unsequenced relative to other volatile accesses, I see no reason such a platform shouldn't be allowed to have a conforming C compiler if that quirk is documented, but there's a huge gap between that and saying that compilers can do whatever they want without having to document anything.
    – supercat
    Jan 30, 2016 at 0:47
1

In a comment to Lorehead, Kaz correctly remarks that this rule only is relevant if the code is otherwise well-defined. You can't make code more Undefined after all, UB is a binary state.

So take the portable uses of volatile, i.e. with signals and longjmp. In both cases the compiler must ensure the proper semantics are being respected. In particular, the update of visible (volatile) global state must be as described in the code. Non-volatile state can be updated in any order.

Now let's assume that a write through int* could affect volatile state. It means any indirect write cannot be reordered anymore. This is a huge performance impact. Even worse, all reads through int* have to be done and cannot be cached in a register.

Thus, this rule allows for significant optimizations, by massively increasing the number of memory operations that can be reordered.

1
  • I fully recognize that it would make sense to have a rule that specifying that if a variable is accessed by a pointer that is not volatile-qualified the access may be reordered with regard to accesses of other volatile variables, and I recognize that in many cases such reordering could cause problems that would end up invoking UB, but if program behavior would be well-defined if a global variable were not declared variable, is there any reason why making the variable volatile should cause the program to invoke UB if nothing actually required that it be volatile?
    – supercat
    Jan 29, 2016 at 23:54
0

Imagine a hypothetical machine with a magic memory location 0xc0103 that, when written to, sets the brightness of a blinkenlight at the machine chassis to the stored value.

You might have a program like this.

volatile uint32_t * pindicator = (volaitle uint32_t *) 0xc0103;

for (;;)
  {
    uint32_t amount = do_some_work();
    *pindicator = amount;
  }

It would make the light flash according to the amount of work currently handled by the system. Even though the program never reads the value of *pindicator, the compiler is not allowed to optimize any store to it away because it is qualified as volatile. The compiler does not know what consequences a store to *pindicator might have but you know and you can write your program accordingly relying on the compiler not getting into your way.

However, if we were to access it via a non-volatile pointer, the compiler would be free to optimize away any store that it can prove has no visible side effect to the program under the as-if rule. In this simple example, the worst this could do is make the light flash in erratic ways or – more likely – stay forever dark. But the C standard doesn't know about blinkenlights and 0xc0103. It cannot say what consequences would be permissible for accessing the variable though a non-volatile pointer. Another variable might refer to a “heartbeat” register that must be incremented at least once per second and failure to do so might cause a safety mechanism to set off a fuse that makes the device explode. So, the standards way of saying “anything or nothing might happen” is to say: “the behavior is undefined”.

1
  • The Standard need not go so far as to say "the behavior is Undefined". It could just as easily (and IMHO more usefully) say that accesses which are not volatile qualified are unsequenced relative to volatile-qualified accesses of other memory locations. On some implementations, accessing volatile locations in the wrong sequence may invoke Undefined Behavior, and on such platforms mixing volatile-qualified and non-qualified accesses would have that as a likely natural consequence, but if an implementation doesn't care about the sequencing of accesses there's no reason the compiler should.
    – supercat
    Jan 30, 2016 at 0:07
0

Quite simply, non-volatile access to volatile variables must be either defined or undefined. So if you want to remove the section that makes it undefined, you must make it defined. I'd be curious how you would do that. For example, just define the behaviour of

int* p = "address of variable which may or may not be volatile";
*p = 1;

in a way that is efficient when *p is not volatile, and defined when *p is volatile. And remember that most uses of pointers fall into this category.

4
  • Incorrect assumption. Unspecified would have been a third option, which is neither defined nor undefined behavior. Implementation defined is a variant thereof. Remember, "Undefined Behavior" can invoke nasal demons. Unspecified is far more constrained.
    – MSalters
    Nov 24, 2015 at 13:01
  • "Unspecified" is required to give possible choices. Which need to be completely defined.
    – gnasher729
    Nov 24, 2015 at 19:13
  • @MSalters: One of the biggest tragedies of the C language is the failure of the Standard to recognize a category of behavior which combined aspects of Unspecified and Implementation-defined behaviors: implementations MUST list any possible consequence, and SHOULD do so as specifically as practical, but MAY list "Undefined Behavior" as a consequence. I'd posit that most forms of UB should have been thus qualified, and the language would be vastly more useful had that been done.
    – supercat
    Jan 30, 2016 at 0:00
  • @MSalters: On the other hand, given how vague the Standard is with regard to volatile variables, accessing a location both with and without the volatile qualifier should be no less implementation-defined than access to volatile-qualified variables in the first place. A conforming implementation may specify (and many do!) that certain volatile variables may only be accessed in certain ways, and any other accesses will invoke Undefined Behavior. I see no reason why such license would not extend to describing what happens if a variable is sometimes accessed as volatile sometimes and not.
    – supercat
    Jan 30, 2016 at 0:04
-1

Non volatile access to a variable is faster as this access can be from local cache, but what you read can have changed else where hence is is dangerous to do this unless you know the value does not change during access.

In case there are 2 non volatile modifications how this is resolved is compiler and hardware dependent.

How these kind of modification are hardware dependent hence the most efficient way to handle it is to leave it undefined so the compiler can handle it the best possible way while letting you benefit for non volatile access to even volatile variables when you know what you are doing to reap speed ups.

2
  • This does not answer the question. To recap, what is the "Benefit of non-volatile access to volatile objects being undefined?". This answer does not identify any such benefit.
    – MSalters
    Nov 24, 2015 at 12:57
  • Made it more explicit. Nov 24, 2015 at 19:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.