C++ new int[0] -- will it allocate memory?
Asked Answered
F

6

280

A simple test app:

cout << new int[0] << endl;

outputs:

0x876c0b8

So it looks like it works. What does the standard say about this? Is it always legal to "allocate" empty block of memory?

Fulvia answered 6/7, 2009 at 13:42 Comment(8)
+1 Very interesting question - although I'm not sure how much it matters in real code.Sidell
@Zifre: I'm asking for curiosity, but it might matter in real world, e.g. when size of allocated memory blocks is calculated in some way, and the result of the calculation might be zero, then there is no direct need to add exceptions to not allocate zero sized blocks.. Because they should be allocated and deleted without errors (if only the zero sized block is not dereferenced). So generally this gives wider abstraction of what a memory block is.Fulvia
@emg-2: In your example situation, it actually wouldn't matter, because delete[] is perfectly legal on a NULL pointer :-).Shaefer
@Evan: you're right :) a better example: it would matter if there would be any if() condition that would depend on NULL value, and e.g. would stop allocating more memory because of wrong assumption, that there isn't any memory left.Fulvia
It's only tangentially related - so I'm commenting here - but C++ in many ways ensures that distinct objects have unique addresses...even if they don't explicitly require storage. A related experiment would be to check the size of an empty struct. Or an array of that struct.Landy
To elaborate on Shmoopty's comment: Especially when programming with templates (e.g. policy class templates like std::allocator), it is common in C++ to have zero-sized objects. Generic code may need to dynamically allocate such objects and use pointers to them to compare object identity. This is why operator new() returns unique pointers for zero-sized requests. While arguably less important/common, the same reasoning applies to array allocation and operator new[]().Calamus
Let's see what happens in real code from the standard library. std::vector<double> v; constructs an empty vector (zero elements). The default allocator doesn't return nullptr (just checked), yet it turn out that v.data() == nullptr.This gives the clue that for the special case of no element the default allocator is not used (or, less likely, overwritten). If you trust the wisdom of the standard library (perhaps Stepanov's insight), that means that it is probably not a good idea to try to allocate zero sized dynamic arrays.(new int[0] uses some memory because the `0 is stored somewhere)Icarus
I bet the standard library has some code like this (for example in the constructor), struct vector : ..., ptr_(num_elements==0?nullptr:allocator_.allocate(num_elements)), ...{...}.Icarus
P
261

From 5.3.4/7

When the value of the expression in a direct-new-declarator is zero, the allocation function is called to allocate an array with no elements.

From 3.7.3.1/2

The effect of dereferencing a pointer returned as a request for zero size is undefined.

Also

Even if the size of the space requested [by new] is zero, the request can fail.

That means you can do it, but you can not legally (in a well defined manner across all platforms) dereference the memory that you get - you can only pass it to array delete - and you should delete it.

Here is an interesting foot-note (i.e not a normative part of the standard, but included for expository purposes) attached to the sentence from 3.7.3.1/2

[32. The intent is to have operator new() implementable by calling malloc() or calloc(), so the rules are substantially the same. C++ differs from C in requiring a zero request to return a non-null pointer.]

Pilsner answered 6/7, 2009 at 13:46 Comment(6)
I get a memory leak if I don't delete. Is it expected? At least I didn't expect.Tridactyl
@EralpB: on the contrary, even if your request is zero, this allocation happens on the Heap, where one request implies several book keeping operations, like allocating and initializing heap guards before and after the zone given by the allocator, insertion into freelists or other complex horrible structures. Freeing it means doing the backward bookkeeping.Selene
@Tridactyl yes i guess you can expect a memory leak every time you don't balance a new[] with a delete[] - whatever the size. In particular, when you call new[i] you need a bit more memory than that which you are trying to alloc in order to store the size of the array (which is later used by delete[] when deallocating)Spaceship
Also, since, incrementing a pointer to point to one element past the last element in a memory block is legal, It should be legal to increment a pointer pointing to this zero sized memory block.Corneliuscornell
@ghd: In the zero-sized case, the begin pointer is the one-past-the-end pointer, so you cannot increment it.Jamnes
@DavidStone: Thanks for correcting me. Indeed, I think it's not possible to do anything with a pointer pointing to a zero-sized memory location, except comparing with itself.Corneliuscornell
F
25

Yes, it is legal to allocate a zero-sized array like this. But you must also delete it.

Fulvia answered 6/7, 2009 at 13:45 Comment(4)
Do you have a citation for this? We all know that int ar[0]; is illegal why is new OK?Perron
It's interesting that C++ isn't so strong with forbidding zero sized objects. Think of the empty base class optimization: Here too, an empty base class sub-object may have size zero. In contrast, the C Standard makes a strong effort to ensure there never are zero sized objects created: In defining malloc(0) it says the effect is of running malloc with some non-zero argument, for example. And in struct { ...; T n[]; }; when there is no space allocated for the array (FAM), it says it behaves as if "n" has one element. (In both cases, using the object in any way is UB, like x.n[0])Arbitral
I think sizeof (type) is expected to never return zero. See for example: https://mcmap.net/q/110238/-can-sizeof-return-0-zeroSpaceship
Even the so-called "empty base class optimization" is only relevant because of an insistence that all objects (as opposed to all bytes within objects) must have unique addresses. C++ could have been made simpler if code that actually cared about objects having unique addresses was required to ensure that they had non-zero size.Selfwill
I
15

What does the standard say about this? Is it always legal to "allocate" empty block of memory?

Every object has a unique identity, i.e. a unique address, which implies a non-zero length (the actual amount of memory will be silently increased, if you ask for zero bytes).

If you allocated more than one of these objects then you'd find they have different addresses.

Ivo answered 6/7, 2009 at 13:49 Comment(10)
"Every object has a unique identity, i.e. a unique address, which implies a non-zero length" - true, but wrong. Object has address, but pointer to the object can point to random memory. "The actual amount of memory will be silently increased, if you ask for zero bytes" - not sure. operator [] also stores the size of the array somewhere (see isocpp.org/wiki/faq/freestore-mgmt#num-elems-in-new-array). So if the implementation allocates the byte count with the data, it could just allocate for the byte count and 0 bytes for data, returning 1-past-last pointer.Macswan
A non-zero length of allocated memory, not a non-zero length of usable memory. My point was that two pointers to two distinct objects shouldn't point to the same address.Ivo
The standard (open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf) does indeed say (3.7.4.1/2) that different calls to operator new[] should return different pointers. BTW, the new expression has some additional rules (5.3.4). I couldn't find any clue that new with 0 size is actually required to allocate anything. Sorry, I downvoted because I find that your answer is not answering the questions, but is providing some controversial statements.Macswan
@Macswan the memory to store the lenght of the block can be implicitly computed using address space. For zero-sized arrays for example it could be possible for the new[] implementation to return addresses in a range where there is no dynamic memory mapped, hence not really using any memory (while using address space)Spaceship
@pqnet: A good quality implementation should allow an unlimited number of new/delete cycles, should it not? On a platform with 64-bit pointers it might be reasonable to say that a computer executing a while(!exitRequested) { char *p = new char[0]; delete [] p; } loop without recycling pointers would collapse into dust before it could possibly run out of address space, but on a platform with 32-bit pointers that would be a far less reasonable assumption.Selfwill
@Selfwill yes of course my proposal makes sense for 64-bit address space, in 32-bit it's pretty rubbish because the address space is cramped.Spaceship
@pqnet: Personally, I think the C and C++ Standards should generalize the concept of "object" down to size zero [an object of size N has N+1 addresses, N of which point to the bytes thereof, and N of which point point "just past" those same bytes, in series] and deprecate code which relies upon zero-byte objects having unique addresses in the absence of a directive or qualifier demanding such treatment. Many programs wouldn't need any special corner-case code to handle zero-sizes cases except for the languages' failure to handle zero-size objects smoothly.Selfwill
@Selfwill the current standard and current implementation is good enough for most uses tbh, we're just discussing on something which doesn't really matter in practice (no one has to allocate that many zero-size objects that it does indeed matter whether they use a byte or not). Adding the concept of multiple addresses, only one of which can actually be used to invoke methods on the object, would require a huge refactoring of the standard wording without any practical advantage. The uniqueness of the pointer returned by new can be exploited to resolve some aliasing and object identity insteadSpaceship
@pqnet: The Standard already recognizes that an N-byte object has N+1 addresses associated with it, but the last address associated with one object may match the first object associated with another. What I am proposing is merely to generalize that down to zero-byte objects, each of which would have one address associated with it, which would be both its start address and its end address, either of which could match the start or end address of any other arbitrary object.Selfwill
@Macswan see this page: cplusplus.com/reference/new/operator%20new[] "If this argument is zero, the function still returns a distinct non-null pointer on success (although dereferencing this pointer leads to undefined behavior)."Asymmetry
S
14

Yes it is completely legal to allocate a 0 sized block with new. You simply can't do anything useful with it since there is no valid data for you to access. int[0] = 5; is illegal.

However, I believe that the standard allows for things like malloc(0) to return NULL.

You will still need to delete [] whatever pointer you get back from the allocation as well.

Shaefer answered 6/7, 2009 at 13:47 Comment(5)
Regarding malloc, you are right - it is implementation defined. This is often seen as a misfeature.Fulvia
I suppose an interesting question is: can the nothrow version of new return NULL if given a size of 0?Shaefer
The semantics of new and malloc are not linked in any way, at least by the standard.Fulvia
not saying they are... was specifically asking about the nothrow version of new.Shaefer
@Evan - the nothrow version of new returns null only if the request fails - not just if the request size is 0 @Neil - not normatively linked - but linked by intent (i.e. operator new may be implemented in terms of malloc but not the other way around) - see the footnote i included in my answerPilsner
W
3

I guarantee you that new int[0] costs you extra space since I have tested it.

For example, the memory usage of

int **arr = new int*[1000000000];

is significantly smaller than

int **arr = new int*[1000000000];
for(int i =0; i < 1000000000; i++) {
    arr[i]=new int[0];
}

The memory usage of the second code snippet minus that of the first code snippet is the memory used for the numerous new int[0].

Wilderness answered 20/12, 2019 at 14:48 Comment(0)
S
2

Curiously, C++ requires that operator new return a legitimate pointer even when zero bytes are requested. (Requiring this odd-sounding behavior simplifies things elsewhere in the language.)

I found Effective C++ Third Edition said like this in "Item 51: Adhere to convention when writing new and delete".

Selectivity answered 27/5, 2016 at 11:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.