0

Say I have defined an unsigned char foo;, which is guaranteed by the standard not to have trap representations. According to this answer, accessing it before its address is taken is still undefined behaviour. Here is the reference from N1570, "6.3.2.1 Lvalues, arrays, and function designators" paragraph 2:

If the lvalue designates an object of automatic storage duration that could have been declared with the register storage class (never had its address taken), and that object is uninitialized (not declared with an initializer and no assignment to it has been performed prior to use), the behavior is undefined.

However, I wonder whether volatile unsigned char foo; invokes undefined behaviour? It seems impossible for a volatile-qualified variable to be stored in a register.


Also, is the following code well-defined?

unsigned char foo;
*&foo -= *&foo;

This question comes from the 102rd footnote:

102) ...... *&E is a function designator or an lvalue equal to E. ......

Community
  • 1
  • 1
nalzok
  • 14,965
  • 21
  • 72
  • 139
  • w.r.t `volatile`, `register` and "stored in a register" are *completely unrelated* concepts in C, and one doesn't imply the other. 6.7.3: "An object that has volatile-qualified type may be modified in ways unknown to the implementation .. What constitutes an access to an object that has volatile-qualified type is implementation-defined." i.e. the point of `volatile` is to go beyond standard-defined behaviour. – Alex Celeste Apr 10 '16 at 15:16
  • "accessing it before its address is taken" - Where did you get that from? There is no requirement for a variable to even have an address unless its address is used (e.g. the `&` operator is applied). And `*&` is identical to not applying any of them, read the whole footnote 102. – too honest for this site Apr 10 '16 at 15:17
  • @Olaf But the standards just writes "... that could have been declared with the register storage class (never had its address taken), ...", and the use of the address taken is not required – nalzok Apr 10 '16 at 15:19
  • `register` just prevents the address from being taken, thus you cannot apply `&` to it. It would not make any sense to have a `auto volatile register` variable. That could not be changed externally (no linkage, etc.) and the compiler even might optimize it out if it can prove it will have no effect (reason why delay-loops with such code might be problematic on a very aggressive compiler). Actually, `auto volatile` alone is already quite useless. – too honest for this site Apr 10 '16 at 15:23
  • @Olaf You mean a `auto volatile register` qualifier/specifier is nearly equivalent to `volatile`, and the only difference is that the address of the variable defined cannot be taken? – nalzok Apr 10 '16 at 15:28
  • Basically yes. But as you cannot take its address, it has no linkage, it cannot be reached from outside and cannot reference something in memory (peripheral register, the most relevant application for `volatile` since C11). So removing its access does not change the observable behaviour. Read your compiler's documentation how it treats `volatile` qualified variables, it should define the exact behaviour. – too honest for this site Apr 10 '16 at 15:32
  • @Olaf Also, is there any difference between `auto volatile` and `volatile`? – nalzok Apr 10 '16 at 15:50
  • @sunqingyao: Sorry, This is not the place to tutor. Just that: `volatile` for automatich variables is ingeneral quite useless (except for the mentioned CPU-cycle delay-loop which is quite a hack anyway). To clarify: This is about variables, not the objects a pointer references, e.g. `volatile int * auto register p;`. – too honest for this site Apr 10 '16 at 15:55
  • @olaf: I don't believe there's a case in modern C where removing an `auto` keyword would makes any difference, and `auto` and `register` may not both appear in the same declaration. – Keith Thompson Apr 10 '16 at 15:59
  • @Olaf Oh I understand now, only pointers to volatile objects are useful! – nalzok Apr 10 '16 at 16:00
  • @KeithThompson: Sorry, I just wanted to emphasise on automatic variables. You are right, of course `auto` is useless resp. wrong with `register` (both are _storage class qualifiers_). This is about `volatile`. – too honest for this site Apr 10 '16 at 16:11
  • @sunqingyao: Not universally true, but only for automatic _pointer_ variables! – too honest for this site Apr 10 '16 at 16:12
  • @Olaf What do you mean by saying "automatic pointer variables"? Pointee variables having automatic storage class? – nalzok Apr 10 '16 at 16:49

1 Answers1

3

First to clarify, the quoted part from 6.3.2.1 addresses a special case, where you have an uninitialized variable which is not necessarily allocated at an address, because the address is never taken. Accessing such an object is explicitly undefined behavior. This is what the linked answer is concerned about.

If that special case does not apply, because the address is taken, then relvant parts would be 6.7.9/10:

If an object that has automatic storage duration is not initialized explicitly, its value is indeterminate.

The definition of indeterminate value 3.19.2:

either an unspecified value or a trap representation

The definition of unspecified value 3.19.3:

valid value of the relevant type where this International Standard imposes no requirements on which value is chosen in any instance

NOTE An unspecified value cannot be a trap representation.

And finally the part that says that accessing a trap representation is UB, 6.2.6.1/5, emphasis mine:

Certain object representations need not represent a value of the object type. If the stored value of an object has such a representation and is read by an lvalue expression that does not have character type, the behavior is undefined. If such a representation is produced by a side effect that modifies all or any part of the object by an lvalue expression that does not have character type, the behavior is undefined.50) Such a representation is called a trap representation.

To sum it up, an indeterminate value is not necessarily a trap representation and therefore, accessing an uninitialized variable is not necessarily undefined behavior.


There is, as far as I know, nothing in the standard explicitly saying that an unsigned character cannot hold a trap representation. 6.2.6.2 only says that they cannot have padding bits. Though in practice this would mean that an unsigned char cannot hold a trap representation, because in order to do so it would either need padding bits or a signed format. So between the lines, I think it is safe to assume that unsigned char cannot hold a trap representation.


However, I wonder whether volatile unsigned char foo; invokes undefined behaviour?

Declaring a variable never invokes undefined behavior in itself. If you access the variable without initializing it, then in the normal case, the value would be indeterminate. And it is implementation-dependent if this would be a trap representation or not.

However, volatile is a special case and none of that applies. Instead you have 6.7.3/7: "... What constitutes an access to an object that has volatile-qualified type is implementation-defined."

So it is simply implementation-defined behavior. Regardless of the type.


Also, is the following code well-defined?

unsigned char foo;
*&foo -= *&foo;

foo has an indeterminate value. It's address is taken. So whether or not this is undefined behavior depends on if the indeterminate value is a trap representation or not, on the given system. As discussed above, I don't think an unsigned char can ever be a trap representation.

However you used the -= operator which would make this expression equivalent to

*&foo = *&foo - *&foo; 

The binary - operator promotes both operands to int during calculation. If there were no trap representations to begin with, there could now be some in the promoted operands, which are of signed type, possibly with padding bits.

Meaning that this particular expression could invoke undefined behavior.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • How can promotion of an unsigned char legitimately yield anything other than a number from 0 to CHAR_MAX? – supercat Apr 12 '16 at 08:13
  • @supercat Realistically it won't. But I don't think it is specified what happens to any padding bits in case an uninitialized variable gets promoted. Maybe they too remain uninitialized, in which case they could hold a trap representation. – Lundin Apr 12 '16 at 08:49
  • For variables of type "unsigned char" I don't think there's much latitude for variables to yield anything else. For any other type, it depends whether you ask a 1990s compiler writer or a hyper-modern compiler writer. A 1990s one would say that specifications of an implementation's general-case behaviors implicitly define what would otherwise be Undefined Behavior, while hyper-modern compiler writers insist that letting programmers ignore corner cases where straightforwardly-generated code would "naturally work" will impede "optimization", and that the Standard's claim that something is... – supercat Apr 12 '16 at 14:37
  • ...Undefined Behavior should be taken as granting compiler writers an irrevocable right to do anything they want with it. – supercat Apr 12 '16 at 14:37