5

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?

Language Lawyer
  • 3,378
  • 1
  • 12
  • 29
  • It seems clear from the definition of past-the-end pointers that `(void*)(&arr + 1) == (void*)(&arr[1] + 1)` must hold, but the status of `&arr[2]` is murky – M.M Jan 13 '20 at 04:03
  • @M.M _It seems clear from the definition of past-the-end pointers that `(void*)(&arr + 1) == (void*)(&arr[1] + 1)` must hold_ Doesn't seem clear to me. – Language Lawyer Jan 13 '20 at 04:05
  • `&arr + 1` represents the address of the first byte in memory after `arr`, and `&arr[1] + 1` represents the address of the first byte in memory after `arr[1]` , those must be the same byte since there is no trailing padding in an array – M.M Jan 13 '20 at 04:06
  • @M.M Ok, if the issue with the first element of the array having the same address as the array [is fixed](https://github.com/cplusplus/draft/pull/3203), then I'd say that for arrays of 2 elements it is clear. But, for arrays with 3 elements, why pointer past the end of `arr[2]` can't compare equal to a pointer to `arr[1]`? – Language Lawyer Jan 13 '20 at 04:11
  • Oh are you trying to make the argument that the array elements might not be in order of their subscripts ? If so then the question is disingenuously framed. FWIW I don't believe the standard supports such a thing and even if it did, it would be a defect that would be corrected if anyone cared to cater to such a level of pedantry. E.g. the thing you just linked to . – M.M Jan 13 '20 at 04:15
  • @M.M Look [here](http://wg21.link/p1510r0#2404): the wording about how class members are allocated was turned into Note. I think it is reasonable to assume that [the wording about how array elements are allocated](http://eel.is/c++draft/dcl.array#6) is also not normative. – Language Lawyer Jan 13 '20 at 04:44
  • @M.M What we have are pointer arithmetic rules and pointer (in)equality comparison rules formulated in terms of pointers pointing to array elements with specific indices. You may also add here the not-yet-existant rule about pointers to the first array elements. And I think, at least for arrays other than of length 1 or 2, this is not enough to prove that the equality in the question shall evaluate to `true`. – Language Lawyer Jan 13 '20 at 04:46
  • @LanguageLawyer or anyone else, do you have local MSVC available? If you replace static assert with output, does the value differ and how? – eerorika Jan 13 '20 at 04:52
  • @LanguageLawyer: I don't understand your question. `&arr + 1` is the second element of the array, not a "past the end" pointer. To the extent that `&arr[2]` is legal C++ syntax, it *shouldn't* be equal to the `&arr + 1`. – Nicol Bolas Jan 13 '20 at 05:02
  • @LanguageLawyer: You completely changed the question, thereby invalidating the answer. – Nicol Bolas Jan 13 '20 at 05:04
  • FWIW, I get the same pointer values in the output using `int arr[2] = {}; std::cout << "(&arr + 1): " << (void*)(&arr + 1) << std::endl; std::cout << "(&arr[2]): " << (void*)(&arr[2]) << std::endl; std::cout << "(arr + 2]): " << (void*)(arr + 2) << std::endl;` in Visual Studio 2008 and Visual Studio 2010. – R Sahu Jan 13 '20 at 05:05
  • @NicolBolas _"You completely changed the question"_ How can you say that, if you don't understand the question? ([_"I don't understand your question"_](https://stackoverflow.com/questions/59710209/shall-a-pointer-past-the-end-of-the-last-array-element-compare-equal-to-a-pointe#comment105573275_59710209)) – Language Lawyer Jan 13 '20 at 05:08
  • @LanguageLawyer: Because the stuff in the answer no longer applies to your question. – Nicol Bolas Jan 13 '20 at 05:12
  • @NicolBolas It still applies, because the answer doesn't mention the exact array length. – Language Lawyer Jan 13 '20 at 05:13
  • What do you get if you display `sizeof(arr)` with all three compilers? – 1201ProgramAlarm Jan 13 '20 at 05:16

1 Answers1

3

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.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • 1
    You seem to be assuming `&arr[2]` to mean the same as `arr + 2` but the standard [doesn't seem clear](https://stackoverflow.com/questions/988158/take-the-address-of-a-one-past-the-end-array-element-via-subscript-legal-by-the/988254#988254) on that – M.M Jan 13 '20 at 04:34
  • 1
    @M.M It appears that I am, and I suppose that well-definedness of the example relies on that. – eerorika Jan 13 '20 at 04:43
  • @LanguageLawyer, the more interesting finding would be wether the code behaves differently with that change in Visual Studio. – R Sahu Jan 13 '20 at 04:49
  • _"Both are pointers to one past the last element of a different complete object - one complete object is the array arr"_ This words are incompatible with what you cite slightly below them. `&arr[2]` (or `arr + 2`) is not a pointer past the end of `arr`, it is a pointer past the end of `arr[1]`. Which is not a complete object. – Language Lawyer Jan 13 '20 at 05:00
  • @M.M I've updated the question. Also, I've changed array length form `2` to `3` – Language Lawyer Jan 13 '20 at 05:03
  • @LanguageLawyer `arr + 1` is a pointer to the last element of `arr`. `arr + 2` is a pointer to the element one past the last element of `arr`. `arr` is a complete object. – eerorika Jan 13 '20 at 05:06
  • @eerorika wording you highlighted in bold says _"… a pointer past the end of the last element of an array x of n elements …"_. `arr + 2` is pointer past the end of the last element of the array `arr`: `arr[1]` (assuming it has 2 elements). But `arr + 2` is not pointer past the end of `arr`. – Language Lawyer Jan 13 '20 at 05:16