Compilers diverge when compiling the code
int main()
{
constexpr int arr[3] = {};
static_assert((void*)(arr + 3) == (void*)(&arr + 1));
}
In GCC and Clang, the static_assert
doesn't fire, MSVC thinks that the static_assert
fails https://godbolt.org/z/dHgmEN
Shall (void*)(arr + 3) == (void*)(&arr + 1)
evaluate to true
and why?
Relevant rules from latest standard draft:
[intro.object]
Objects can contain other objects, called subobjects. A subobject can be a member subobject ([class.mem]), a base class subobject ([class.derived]), or an array element. An object that is not a subobject of any other object is called a complete object.
So, array elements are subobjects. The array in the example is a complete object.
[expr.eq]
... Comparing pointers is defined as follows:
If one pointer represents the address of a complete object, and another pointer represents the address one past the last element of a different complete object,79 the result of the comparison is unspecified.
Otherwise, if ... both represent the same address, they compare equal.
79) As specified in [basic.compound], an object that is not an array element is considered to belong to a single-element array for this purpose
The first case seems to match almost, but not quite. Both are pointers to one past the last element of a different complete object - one complete object is the array arr
, the other is the hypothetical single element array whose element is arr
. And neither is a pointer to the unrelated complete object that might exist after their respective array as clarified by the note in the following [basic.compound] quote.
So, the other case should apply assuming they represent the same address.
[basic.compound]
A value of a pointer type that is a pointer to or past the end of an object represents the address of the first byte in memory ([intro.memory]) occupied by the object43 or the first byte in memory after the end of the storage occupied by the object, respectively. [ Note: A pointer past the end of an object ([expr.add]) is not considered to point to an unrelated object of the object's type that might be located at that address. A pointer value becomes invalid when the storage it denotes reaches the end of its storage duration; see [basic.stc]. — end note ]
For purposes of ... comparison ([expr.rel], [expr.eq]), a pointer past the end of the last element of an array x of n elements is considered to be equivalent to a pointer to a hypothetical array element n of x and an object of type T that is not an array element is considered to belong to an array with one element of type T. ...
43) For an object that is not within its lifetime, this is the first byte in memory that it will occupy or used to occupy.
Both arr
, and the hypothetical array containing just arr
have the same address and the same size. Therefore one element past both arrays is considered to be equivalent to a pointer at the same address outside the bounds of the array.
Just to be clear that an array cannot contain padding:
[expr.sizeof]
... When applied to a class, the result is the number of bytes in an object of that class including any padding required for placing objects of that type in an array. The result of applying sizeof to a potentially-overlapping subobject is the size of the type, not the size of the subobject. When applied to an array, the result is the total number of bytes in the array. This implies that the size of an array of n elements is n times the size of an element.
Let us clarify that conversion to void*
should not change pointer value and thus the equality.
[conv.ptr]
A prvalue of type “pointer to cv T”, where T is an object type, can be converted to a prvalue of type “pointer to cv void”. The pointer value ([basic.compound]) is unchanged by this conversion.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With