260

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?

  • 4
    +1 Very interesting question - although I'm not sure how much it matters in real code. – Zifre Jul 06 '09 at 13:46
  • 40
    @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. –  Jul 06 '09 at 13:59
  • 2
    @emg-2: In your example situation, it actually wouldn't matter, because delete[] is perfectly legal on a NULL pointer :-). – Evan Teran Jul 06 '09 at 14:01
  • @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. –  Jul 06 '09 at 14:10
  • 2
    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. – Drew Dormann Jul 06 '09 at 15:47
  • 2
    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[](). – Trevor Robinson Jan 13 '10 at 20:46
  • Let's see what happens in real code from the standard library. `std::vector 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) – alfC Dec 08 '15 at 17:14
  • 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)), ...{...}`. – alfC Dec 08 '15 at 17:17

6 Answers6

247

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.]

SCFrench
  • 7,984
  • 2
  • 26
  • 54
Faisal Vali
  • 29,823
  • 8
  • 40
  • 45
  • 1
    I get a memory leak if I don't delete. Is it expected? At least I didn't expect. – EralpB Mar 10 '14 at 13:46
  • 12
    @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. – v.oddou Jun 13 '14 at 12:18
  • 3
    @EralpB 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) – pqnet Oct 20 '17 at 15:51
24

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

  • 1
    Do you have a citation for this? We all know that `int ar[0];` is illegal why is new OK? – Motti Jul 06 '09 at 13:47
  • 4
    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]) – Johannes Schaub - litb Jul 06 '09 at 16:25
  • 1
    I think `sizeof (type)` is expected to never return zero. See for example: https://stackoverflow.com/questions/2632021/can-sizeof-return-0-zero – pqnet Oct 20 '17 at 15:54
  • 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. – supercat Jun 27 '18 at 21:26
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.

ChrisW
  • 51,820
  • 11
  • 101
  • 201
  • "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 https://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. – Steed Apr 13 '16 at 12:31
  • 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. – ChrisW Apr 13 '16 at 13:50
  • 1
    The standard (http://www.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. – Steed Apr 13 '16 at 15:31
  • @Steed 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) – pqnet Oct 20 '17 at 16:00
  • @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. – supercat Jun 27 '18 at 21:30
  • @supercat 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. – pqnet Jul 04 '18 at 13:15
  • @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. – supercat Jul 04 '18 at 16:04
  • @supercat 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 instead – pqnet Jul 05 '18 at 11:44
  • @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. – supercat Jul 05 '18 at 18:22
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.

Evan Teran
  • 80,654
  • 26
  • 169
  • 231
  • 5
    Regarding malloc, you are right - it is implementation defined. This is often seen as a misfeature. –  Jul 06 '09 at 13:49
  • I suppose an interesting question is: can the nothrow version of new return NULL if given a size of 0? – Evan Teran Jul 06 '09 at 13:52
  • 1
    The semantics of new and malloc are not linked in any way, at least by the standard. –  Jul 06 '09 at 13:53
  • not saying they are... was specifically asking about the nothrow version of new. – Evan Teran Jul 06 '09 at 13:55
  • @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 answer – Faisal Vali Jul 06 '09 at 14:07
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".

shuaihanhungry
  • 399
  • 4
  • 6
1

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].

Shi Jieming
  • 521
  • 6
  • 5