21

I'm trying to understand the following paragraph from Stroustrup's "The C++ Programming Language" on page 282 (emphasis is mine):

To deallocate space allocated by new, delete and delete[] must be able to determine the size of the object allocated. This implies that an object allocated using the standard implementation of new will occupy slightly more space than a static object. At a minimum, space is needed to hold the object’s size. Usually two or more words per allocation are used for free-store management. Most modern machines use 8-byte words. This overhead is not significant when we allocate many objects or large objects, but it can matter if we allocate lots of small objects (e.g., ints or Points) on the free store.

Note that the author doesn't differentiate whether the object is an array, or not, in the sentence highlighted above.

But according to paragraph §5.3.4/11 in C++14, we have (my emphasis):

When a new-expression calls an allocation function and that allocation has not been extended, the new-expression passes the amount of space requested to the allocation function as the first argument of type std::size_t. That argument shall be no less than the size of the object being created; it may be greater than the size of the object being created only if the object is an array.

I may be missing something, but it seems to me, we have a contradiction in those two statements. It was my understanding that the additional space required was only for array objects, and that this additional space would hold the number of elements in the array, not the array size in bytes.

John Kalane
  • 1,163
  • 8
  • 17
  • 4
    Stroustrup is talking about implementation and the standard is talking about the new/delete API. There is no contradiction. – mattiash Nov 03 '15 at 06:37
  • I feel like Pete's answer hits the nail on the head the [as-if rule](http://en.cppreference.com/w/cpp/language/as_if) only requires the implementation emulate the observable behavior. the as-if rule arguably even allows an [implementation of optimize away heap allocations](http://stackoverflow.com/q/31873616/1708801). – Shafik Yaghmour Nov 04 '15 at 15:52

6 Answers6

22

If you call new on a type T, the overloaded operator new that may be invoked will be passed exactly sizeof(T).

If you implement a new of your own (or an allocator) that uses some different memory store (ie, not just forwarding to another call to new or malloc etc), you'll find yourself wanting to store information to clean up the allocation later, when the delete occurs. A typical way to do this is to get a slightly larger block of memory, and store the amount of memory requested at the start of it, then return a pointer to later in the memory you acquired.

This is roughly what most standard implementations of new (and malloc do).

So while you only need sizeof(T) bytes to store a T, the amount of bytes consumed by new/malloc is more than sizeof(T). This is what Stroustrup is talking about: every dynamic allocation has actual overhead, and that overhead can be substantial if you make lots of small allocations.


There are some allocators that don't need that extra room "before" the allocation. For example, a stack-scoped allocator that doesn't delete anything until it goes out of scope. Or one that allocates from stores of fixed-sized blocks and uses a bitfield to describe which are in use.

Here, the accounting information isn't store adjacent to the data, or we make the accounting information implicit in the code state (scoped allocators).


Now, in the case of arrays, the C++ compiler is free to call operator new[] with an amount of memory requested larger than sizeof(T)*n when T[n] is allocated. This is done by new (not operator new) code generated by the compiler when it asks your overload for memory.

This is traditionally done on types with non-trivial destructors so that the C++ runtime can, when delete[] is called, iterate over each of the items and call .~T() on them. It pulls off a similar trick, where it stuffs n into memory before the array it is using, then does pointer arithmetic to extract it at delete time.

This is not required by the standard, but it is a common technique (clang and gcc both do it at least on some platforms, and I believe MSVC does as well). Some method of calculating the size of the array is needed; this is just one of them.

For something without a destructor (like char) or a trivial one (like struct foo{ ~foo()=default; }, n isn't needed by the runtime, so it doesn't have to store it. So it can say "naw, I won't store it".

Here is a live example.

struct foo {
  static void* operator new[](std::size_t sz) {
    std::cout << sz << '/' << sizeof(foo) << '=' << sz/sizeof(foo) << "+ R(" << sz%sizeof(foo) << ")" << '\n';
    return malloc(sz);
  }
  static void operator delete[](void* ptr) {
    free(ptr);
  }
  virtual ~foo() {}
};

foo* test(std::size_t n) {
  std::cout << n << '\n';
  return new foo[n];
}

int main(int argc, char**argv) {
  foo* f = test( argc+10 );
  std::cout << *std::prev(reinterpret_cast<std::size_t*>(f)) << '\n';
}

If run with 0 arguments, it prints out 11, 96/8 = 12 R(0) and 11.

The first is the number of elements allocated, the second is how much memory is allocated (which adds up to 11 element's worth, plus 8 bytes -- sizeof(size_t) I suspect), the last is what we happen to find right before the start of the array of 11 elements (a size_t with the value 11).

Accessing memory before the start of the array is naturally undefined behavior, but I did it in order to expose some implementation details in gcc/clang. The point is that they did ask for an extra 8 bytes (as predicted), and they did happen to store the value 11 there (the size of the array).

If you change that 11 to 2, a call to delete[] will actually delete the wrong number of elements.

Other solutions (to store how big the array is) are naturally possible. As an example, if you know you aren't calling an overload of new and you know details of your underlying memory allocation, you could reuse the data it uses to know your block size to determine the number of elements, thus saving an extra size_t of memory. This requires knowing that your underlying allocator won't over-allocate on you, and that it stores the bytes used at a known offset to the data-pointer.

Or, in theory, a compiler could build a separate pointer->size map.

I am unaware of compilers that do either of these, but would be surprised by neither.

Allowing this technique is what the C++ standard is talking about. For array allocation, the compiler's new (not operator new) code is permitted to ask operator new for extra memory. For non-array allocation, the compiler's new is not permitted to ask operator new for extra memory, it must ask for the exact amount. (I believe there may be exceptions for memory-allocation merging?)

As you can see, the two situations are different.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Wrong. Runtime does not do this - I mean, it DOES NOT store the number of elements in array. – SergeyA Nov 02 '15 at 20:34
  • 6
    @SergeyA [Except it does](http://coliru.stacked-crooked.com/a/b35020d1134cbe20). See that 11 I print at the bottom by accessing 1 before the start of the array I allocated? That is the number of elements I allocated. See the 96, = 11*8 + 8? That is what was requested from `new`. An extra 8 bytes. Which happens to equal the number of elements in the array. – Yakk - Adam Nevraumont Nov 02 '15 at 20:58
  • @Yakk `In the case of arrays, the C++ compiler is free to call new with an amount of memory requested larger than sizeof(T)*n when T[n] is allocated.` That's what I thought. But reading §5.3.4/11 more careffully I can't find any specific reference to the number of elements in the array. Could you explain this? – John Kalane Nov 02 '15 at 21:23
  • 5
    @JohnKalane There is no mandate to store the number of elements in the array in that extra storage. It just happens to be something compilers actually do. The problem is that on `delete[]`, the compiler gets a pointer to the first element, and needs to know how to destroy each element. There are a myriad of ways to solve this problem (a separate table, for example), but the simplest is storing the number of elements *before* the buffer. Then, when deleted, you know the type and the size, and can hence iterate over each element. The standard *does not mandate this*, but *it allows it*. – Yakk - Adam Nevraumont Nov 02 '15 at 21:30
  • 1
    @JohnKalane And, as it happens, it is what both gcc and clang do. I believe so does MSVC. Now, with the default allocator, they could in theory use the fact that whatever allocates it also needs to store how big the block is, and use *that* information to determine how big the array is. This requires tight coupling with the allocation system, and I do not know if it is actually done. For an overloaded `new` it is, however, not practical. – Yakk - Adam Nevraumont Nov 02 '15 at 21:31
  • @Yakk That was a very good answer, but I'll have to accept David Schwartz's, since he was very clear from the beginning, in showing my confusion between the lower level details that Stroustrup mentions in his book and the Standard, which writes about the size to be passed to the allocation function. Two completely different things. Thanks (+1). – John Kalane Nov 02 '15 at 21:38
  • @Yakk, a memory allocator (for example malloc) often doesn't store the _exact_ size of the allocation but rounds up. So whether I allocate 9999 or 10000 four-byte objects, it is likely that 40,000 bytes will be allocated, with no way to find out the exact number of objects. – gnasher729 Nov 02 '15 at 23:18
  • You could broaden that to "no destructor (like `char`) or a trivial destructor". – Deduplicator Nov 02 '15 at 23:24
  • @gnasher729 sure, but that is an implementation detail. The backing allocator *could* (I dunno if some do) store the exact size, and the non-operator `new` code could know this, and avoid duplicate overhead. But that is an implementation detail, not constrained by the standard either way. – Yakk - Adam Nevraumont Nov 03 '15 at 00:02
  • @Deduplicator I think `struct foo{ ~foo()=default; };` has a trivial dtor; am I right? (of course, under as-if, skipping the element count on a class with a dtor that is as-if trivial could be possible). – Yakk - Adam Nevraumont Nov 03 '15 at 00:03
  • Yes, the rule is: All members trivial and implicit defined or defaulted in-class => trivial. – Deduplicator Nov 03 '15 at 00:19
21

There's no contradiction between these two things. The allocation function gets the size, and almost certainly has to allocate a bit more than that so it knows the size again if the deallocation function is called.

When an array of objects that have a non-trivial destructor is allocated, the implementation needs some way to know how many times to call the destructor when delete[] is called. Implementations are permitted to allocate some extra space along with the array to store this additional information though not every implementation works this way.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
  • But the Standard is clear: `That argument shall be no less than the size of the object being created; it may be greater than the size of the object being created **only** if the object is an array.` How do you explain this? – John Kalane Nov 02 '15 at 20:29
  • @JohnKalane, but what is your question? Standard just says, that when you write `X* x = new X` new operator will call an allocation function, and provide it with size_t argument which is no less than `sizeof(X)` (and since it is not an array, it will be exactly sizeof(X). – SergeyA Nov 02 '15 at 20:32
  • @JohnKalane If the object is an array, `delete[]` needs to know how many times to call the destructor, so higher-level code (above the allocator) may need extra space. That has nothing to do with the allocator needing space to know the size of the allocation. – David Schwartz Nov 02 '15 at 20:32
  • @DavidSchwartz, stop saying that. Implementations DO NOT store the size of the array. THEY DO NOT DO IT! – SergeyA Nov 02 '15 at 20:36
  • 7
    Then how does the implementation know how many times to call the destructor? It can't get the size from the allocator because there is no "get size from allocator" function that the allocator implements. Heck, the allocator might round up the size it stores to the nearest 128KB. – David Schwartz Nov 02 '15 at 20:44
  • @DavidSchwartz `If the object is an array, delete[] needs to know how many times to call the destructor, so higher-level code (above the allocator) may need extra space.` But reading §5.3.4/11 more carefully I can see that the extra-space passed to the allocation function is due only to alignment issues. It just doesn't say anything about the number of elements in the array. – John Kalane Nov 02 '15 at 21:05
  • @JohnKalane: Where does it say that this is due *only* to alignment issues? There's just an *extra* constraint for `char` and `unsigned char`. I don't see how this invalidates anything which David said. – Christian Hackl Nov 02 '15 at 21:15
  • @ChristianHackl I'm looking at bullet-points (14.3) and (14.4) to be more precise. I don't see there any reference that the extra-memory `x` and `y` are due to the number of elements in the array, nor can I see anything related to this in §5.3.4/11. – John Kalane Nov 02 '15 at 21:19
  • 2
    @JohnKalane: But those bullet points concern only the *interface* of `new[]`, i.e. the view from the outside, from client code. All the discussion about storing the number of elements is about the *implementation* of `new[]`. – Christian Hackl Nov 02 '15 at 21:29
  • @DavidSchwartz: If an implementation can tell that the default allocator is being used, the default allocator records the exact length of allocation requests, and the implementation knows how to find out the exact length of something created using the default allocator, or if the implementation knows that the type created with new[] requires only default destruction, would the implementation need its own copy of the length separate from the allocator? – supercat Nov 02 '15 at 22:19
  • 1
    @supercat No. But an implementation has to have some fallback if the default allocator isn't being used. The standard permits an implementation to use extra space from the allocator for this purpose but the implementation is free to solve the problem some other way. – David Schwartz Nov 02 '15 at 22:24
3

There is no contradiction between the two paragraphs.

The paragraph from the Standard discusses the rules of the first argument being passed to the allocation function.

The paragraph out of Stroustrup doesn't talk focus on the first argument having type std::size_t but explains the allocation itself which is "two or more words" bigger than what new indicates, and that every programmer should know.

Stroustrup's explanation is more low level, that's the difference. But there is no contradiction.

dspfnder
  • 1,135
  • 1
  • 8
  • 13
3

The quote from the standard is talking about the value passed to operator new; the quote from Stroustrup is talking about what operator new does with the value. The two are pretty much independent; the requirement is only that the allocator allocate at least as much storage as was requested. Allocators often allocate more space than was requested. What they do with that extra space is up to the implementation; often it's just padding. Note that even if you read the requirements narrowly, that the allocator must allocate the exact number of bytes requested, allocating more is allowed under the "as if" rule, because no portable program can detect how much memory was in fact allocated.

Pete Becker
  • 74,985
  • 8
  • 76
  • 165
2

I'm not sure that both talk about the same thing...

It seems that Stroustrup is talking about a more general memory allocation, that inherently uses extra data to manage free/allocated chunks. I think he is not talking about the value of the size passed to new but what really happens at some lower level. He probably would say: when you ask for 10 bytes, the machine will probably use slightly more than 10 bytes. using the standard implementation seems to be important here.

While the standard talks about the value passed to the function.

One talks about implementation while the other not.

Jean-Baptiste Yunès
  • 34,548
  • 4
  • 48
  • 69
0

There is no contradiction, because "precisely the object's size" is one possible implementation of "at a minimum, the size of the object".

The number 42 is at least 42.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055