5
votes

This question is inspired by answers to this question.

Following code has potential for undefined behaviour:

uint64_t arr[1]; // Uninitialized
if(arr[0] == 0) {

C standard specifies that uninitialized variable with automatic storage duration has indeterminate value, which is either unspecified or trap representation. It also specifies that uintN_t types have no padding bits, and size and range of values are well defined; so trap representation for uint64_t is not possible.

So I conclude that uninitialized value itself is not undefined behavior. What about reading it?

6.3.2.1 Lvalues, arrays, and function designators

  1. ...
  2. Except when it is the operand of the sizeof operator, the _Alignof operator, the unary & operator, the ++ operator, the -- operator, or the left operand of the. operator or an assignment operator, an lvalue that does not have array type is converted to the value stored in the designated object (and is no longer an lvalue); this is called lvalue conversion. ...

    ... 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.

  3. Except when it is the operand of the sizeof operator, the _Alignof operator, or the unary & operator, or is a string literal used to initialize an array, an expression that has type ‘‘array of type’’ is converted to an expression with type ‘‘pointer to type’’ that points to the initial element of the array object and is not an lvalue. If the array object has register storage class, the behavior is undefined.

Question: Does subscripting array count as taking the address of an object?

Following text seems to imply that subscripting array requires conversion to a pointer, which seems impossible to do without taking address:

6.5.2.1 Array subscripting

Constraints

  1. One of the expressions shall have type ‘‘pointer to complete object type’’, the other expression shall have integer type, and the result has type ‘‘type’’.

Semantics

  1. A postfix expression followed by an expression in square brackets [] is a subscripted designation of an element of an array object. The definition of the subscript operator [] is that E1[E2] is identical to (*((E1)+(E2))). Because of the conversion rules that apply to the binary + operator, if E1 is an array object (equivalently, a pointer to the initial element of an array object) and E2 is an integer, E1[E2] designates the E2- the element of E1 (counting from zero).

This makes §6.3.2.1 paragraph 3 seem weird. How could array have register storage class at all, if subscription requires conversion to a pointer?

1
This is a duplicate of stackoverflow.com/q/11962457/4389800 (for Q1) and stackoverflow.com/q/17342881/4389800 (for Q2).P.P
footnote 121 says that the only allowed operations for an array declared with register storage class are sizeof and _Alignof. I.e. not useful at all. Naturally, otherwise the behaviour is just undefined, it doesn't mean that an implementation couldn't supply some reasonable and usable behaviour for that case.Antti Haapala
@P.P. I have to disagree. First seems to cover aspects I already knew, and I feel the second doesn't cover language lawyering at all from this questions perspective.user694733
@AnttiHaapala That's an excellent find. That would mean that arr cannot be placed in register.user694733

1 Answers

6
votes

Yes, array subscripting counts as taking the address, as per the part you quoted in 6.5.2.1. The expression E1 must have its address taken.

Therefore the special case of UB in 6.3.2.1 does not apply to array indexing. If array indices are used, it is not relevant if the array could be stored with register storage duration or not (a variable having its address taken cannot use register storage duration).

You are correct in assuming that reading an uninitialized stdint.h type with indeterminate value, which has its address taken, does not invoke undefined behavior (guaranteed by C11 7.20.1.1), but merely unspecified behavior. The value could be anything and it can be non-deterministic between several reads, but it cannot be a trap.

"Reading an uninitalized variable is always UB" is a wide-spread but incorrect myth. Further information with normative sources in this answer.