问题
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
- ...
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.
- 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
- 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
- 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:
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.
回答2:
The lvalues discussed in 6.3.2.1 paragraph 2 can never be elements of an array declared with register
, so the question of what it means to take their addresses never arises.
Per paragraph 3, using an array declared with register
other than as the operand of sizeof
, _Alignof
, or &
has undefined behavior. But the only way to get an lvalue for an array element is to use an array in a way other than these, as in arr[0]
.
Therefore, it can never occur (with defined behavior), that the lvalue discussed in paragraph 2 designates an array element that could have been declared with register storage class.
In this light, the parenthetical comment “never had its address taken” is clear. It applies only to objects that are not array elements, and the only way to produce the address of such an object is to apply &
.1 Taking the address means applying &
.
Whether evaluating arr[0]
or arr + 2
or even simply arr
(as in arr;
, not as an operand of sizeof
or anything else) constitutes taking the address of an element of the array is irrelevant. It has no effect on what 6.3.2.1 2 means.
Regarding your question about how an array could have register storage class, note 121 (in clause 6.7.1) says “Thus, the only operators that can be applied to an array declared with storage-class specifier register are sizeof and _Alignof.” (While this is legal, it does not seem useful, as you can apply sizeof
and _Alignof
without using register
, so including register
in the declaration does not seem to provide any useful property.)
Footnote
1 An exception is that clause 6.7.2.1 paragraph 15 says that a pointer to a structure may be converted to a pointer to its first member, and paragraph 16 says a pointer to a union may be converted to a pointer to any of its members. However, this requires having a pointer to the aggregate (the structure or the union), which means the aggregate itself cannot have register storage class, and members inside the aggregate cannot be declared with a storage class.
来源:https://stackoverflow.com/questions/47884469/does-array-subscription-count-as-taking-address-of-object