22

Consider

int main()
{
    auto a = new int[0];
    delete[] a; // So there's no memory leak
}

Between the copy initialisation and deletion, are you allowed to read the pointer at a + 1?

Furthermore, does the language permit the compiler to set a to nullptr?

Bathsheba
  • 220,365
  • 33
  • 331
  • 451
  • As fr as I know, you're not allowed to read neither a not a+1. – Fareanor Feb 27 '20 at 21:50
  • 2
    @Fareanor: You can read `a` for sure (you certainly can't dereference it). – Bathsheba Feb 27 '20 at 21:52
  • I'm asking this in response to my comment to https://stackoverflow.com/questions/60441437/should-i-free-a-0-size-dynamic-array/60441472#60441472 – Bathsheba Feb 27 '20 at 21:52
  • delete `a` sets `a` to NULL – Rasmi Ranjan Nayak Feb 27 '20 at 21:53
  • 8
    @RasmiRanjanNayak: That's hogwash. – Bathsheba Feb 27 '20 at 21:53
  • 2
    @RasmiRanjanNayak Oh my ... no – Ted Lyngmo Feb 27 '20 at 21:54
  • @Bathsheba Oh right I meant "dereference" instead of "read". My bad. – Fareanor Feb 27 '20 at 21:54
  • Since C++ follows the "you don't get what you don't use/need" principle, I'm not sure a compiler would set `a` to `nullptr`. But it is only a guess. – Fareanor Feb 27 '20 at 21:57
  • I'm struggeling to understand what you mean exactly with "_read the pointer at `a + 1`_". Regarding the second question, it seems a decision was made before C++11: https://stackoverflow.com/a/3389428/7582247 – Ted Lyngmo Feb 27 '20 at 21:58
  • 1
    @TedLyngmo: When I say "read the pointer at `a + 1`", is the following code `auto b = a + 1;` undefined behaviour? (I think it is). – Bathsheba Feb 27 '20 at 21:58
  • @Bathsheba Ah, ok. I was looking for traps :-) – Ted Lyngmo Feb 27 '20 at 21:59
  • What is the point of `new int[0]` in the first place? – Ayxan Haqverdili Feb 27 '20 at 22:03
  • @Kit.: I'm thinking on those lines too. (And therefore I can't see why `nullptr` can't be a choice for `a`.) – Bathsheba Feb 27 '20 at 22:05
  • 2
    @Ayxan I'd guess in the case where `0` is actually the result of some expression that you won't know until runtime. Since `new int[0]` is safe, it could save you worrying about some branching/special cases. Imagine if I were to initialize an `std::vector` with `std::vector v(0);`. – scohe001 Feb 27 '20 at 22:05
  • Really, there is nothing special about how `new` works with a zero sized array relative to a non-zero sized array. How you handle it is basically homogeneous with how you handle any other dynamic array allocations. Edit : I'll admit it feels strange that `a` is both a pointer to the start of the array *and* a one-past-the-end pointer, but that happens with any empty range, like `begin()` and `end()` of an empty container. – François Andrieux Feb 27 '20 at 23:43
  • There's no allocated memory at `a + 1`, let alone a pointer. Did you mean to ask about the value of `a + 1` ? – M.M Mar 11 '20 at 21:17
  • @M.M not the object to which `a + 1` may be pointing, but rather the pointer itself. – Bathsheba Mar 11 '20 at 21:18
  • [Related](https://stackoverflow.com/q/1087042/10957435), but not exactly a duplicate, since you have a more specific question. –  Mar 14 '20 at 05:59

2 Answers2

24

Per recent CWG reflector discussion as a result of editorial issue 3178, new int[0] produces what is currently called a "past-the-end" pointer value.

It follows that a cannot be null, and a + 1 is undefined by [expr.add]/4.

T.C.
  • 123,516
  • 14
  • 264
  • 384
  • 5
    _"Per recent CWG reflector discussion as a result of editorial issue 3178, `new int[0]` produces what is currently called a "past-the-end" pointer value"_ This is not accurate. There wasn't much of discussion. One person suggested that the value should be "pointer past the end of an object", and this statement was questioned because in case of an array with `0` elements, there is no object to be past the end of. – Language Lawyer Feb 29 '20 at 16:07
  • @LanguageLawyer there seems no doubt that `a + 1` is undefined in any case, so your point only pertains to whether `a` can be null? – M.M Mar 11 '20 at 21:22
  • There did not appear to be any disagreement about the intended semantics, nor that the current name for this category of pointer values is a poor fit for this scenario. – T.C. Mar 11 '20 at 21:39
  • @M.M I think that `a + 1` is undefined and maybe the resulting pointer value will be called "pointer past the end". I'm not saying that the answer is wrong. It is slightly not accurate because it could be understood as that there was a lengthy discussion with a consensus that mere specifying that the result is "pointer past the end" is enough to resolve the issue. The problem with "pointer past the end" is that it is past the end of some object and subtracting `1` from such a pointer value gives one a pointer to the object, which probably shouldn't be the case for arrays of `0` elements. – Language Lawyer Mar 12 '20 at 09:49
  • I'd be tempted to award this answer but the first sentence is not sufficiently authoritative. Although the second one does indeed follow logically from the first, if only the first could be adequately established. I know that bounties don't directly motivate us 100k'ers but it would be nice if you were able to spruce up this answer: you're clearly onto something. – Bathsheba Mar 12 '20 at 21:19
  • 2
    @Bathsheba Do you think there could be anything more authoritative about defective wording? – Language Lawyer Mar 13 '20 at 12:02
5
    auto a = new int[0];

According to [basic.compound.3], the value stored in a must be one of the following:

  1. A pointer to an object (of type int)
  2. A pointer past the end of an object
  3. Null
  4. Invalid

We can rule out the first possibility since there were no objects of type int constructed. The third possibility is ruled out since C++ requires a non-null pointer to be returned (see [basic.stc.dynamic.allocation.2]). Thus we are left with two possibilities: a pointer past the end of an object or an invalid pointer.

I would be inclined to view a as a past-the-end pointer, but I don't have a reputable reference to definitively establish that. (There is, though, a strong implication of this in [basic.stc], seeing how you can delete this pointer.) So I'll entertain both possibilities in this answer.

Between the copy initialisation and deletion, are you allowed to read the pointer at a + 1?

The behavior is undefined, as dictated by [expr.add.4], regardless of which possibility from above applies.

If a is a past-the-end pointer, then it is considered to point to the hypothetical element at index 0 of an array with no elements. Adding the integer j to a is defined only when 0≤0+j≤n, where n is the size of the array. In our case, n is zero, so the sum a+j is defined only when j is 0. In particular, adding 1 is undefined.

If a is invalid, then we cleanly fall into "Otherwise, the behavior is undefined." (Not surprisingly, the cases that are defined cover only valid pointer values.)

Furthermore, does the language permit the compiler to set a to nullptr?

No. From the above-mentioned [basic.stc.dynamic.allocation.2]: "If the request succeeds, the value returned by a replaceable allocation function is a non-null pointer value". There is also a footnote calling out that C++ (but not C) requires a non-null pointer in response to a zero request.

JaMiT
  • 9,693
  • 2
  • 12
  • 26
  • 1
    If it were an invalid pointer value, you'll have serious problems from http://eel.is/c++draft/basic.stc#4 – T.C. Mar 17 '20 at 16:51
  • @T.C. True, that does strongly imply that it cannot be an invalid pointer value. – JaMiT Mar 17 '20 at 22:07