9

Is there any difference between fncs: operator new and operator new[] (NOT new and new[] operators)? Except of course call syntax? I'm asking because I can allocate X number of bytes for my objs with ::operator new(sizeof(T)*numberOfObject) and then access them with array notation, so what's the big deal with ::operator new[]. Is it only syntactic sugar?

#include <new>
#include <iostream>
#include <malloc.h>

using namespace std;
struct X
{
  int data_;
  X(int v):data_(v){}
};
int _tmain(int argc, _TCHAR* argv[])
{
  unsigned no = 10;
  void* vp = ::operator new(sizeof(X) * no);
  cout << "Mem reserved: " << _msize(vp) << '\n';
  X* xp = static_cast<X*>(vp);
  for (unsigned i = 0; i < no; ++i)
  {
    new (xp + i) X(i);
  }
  for (unsigned i = 0; i < no; ++i)
  {
    cout << (xp[i]).data_ << '\n';
  }
  for (unsigned i = 0; i < no; ++i)
  {
    (xp + i)->~X();
  }
  ::operator delete(vp);
  return 0;
}
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
There is nothing we can do
  • 23,727
  • 30
  • 106
  • 194
  • 2
    Apart from your code not compiling with a standard C++ compiler (look up the signature of `main`, please) it doesn't illustrate using `operator new[]`. Perhaps you posted code that belongs to some *other question*? – Cheers and hth. - Alf Dec 11 '10 at 17:28
  • 2
    It's also good not to post example code that includes a memory leak in a question on memory allocation functions. (unless you're asking whether there is a leak) – Ben Voigt Dec 11 '10 at 17:31
  • @Alf yes, sorry for that. I use VS. Should've said that – There is nothing we can do Dec 11 '10 at 17:31
  • See also: http://stackoverflow.com/questions/4390356/using-malloc-versus-new – Marcus Borkenhagen Dec 11 '10 at 17:40
  • 3
    You replaced a memory leak with what I consider an illegal use of `delete`. The standard says "The *delete-expression* operator destroys a most derived object or array created by a *new-expression*." vp does point to an object created by a *new-expression*, but it was a placement new and the calls to new and delete are mismatched. Specifically, calling `delete vp;` that way does not undo the line `vp = ::operator new(sizeof(X) * no);` which precedes it. – Ben Voigt Dec 11 '10 at 18:08
  • @Ben So how would you go about it? I mean in what way you would deallocate it? Would you first call explicitly dtor for each of those obj and then call delete vp? – There is nothing we can do Dec 11 '10 at 18:12
  • @UncleBens thanks but your call doesn't call dtors of X. – There is nothing we can do Dec 11 '10 at 18:18
  • @There: @Ben: Should you not use the ::operator delete(vp)? The method of deletion must match the method of allocation. My thinking cap is not full yon yet! – Martin York Dec 11 '10 at 18:25
  • @There: Neither did yours (or at least, it called only the first one). – Ben Voigt Dec 11 '10 at 18:25
  • @Martin: look at revision 3 of the question. – Ben Voigt Dec 11 '10 at 18:25
  • @Ben and Uncle I've edited (and fixed) correct way of destructing and deallocating mem allocated with ::operator new() – There is nothing we can do Dec 11 '10 at 18:27
  • And about the "I consider an illegal use of delete"... I created a question out of that. http://stackoverflow.com/questions/4418220/legality-of-using-operator-delete-on-a-pointer-obtained-from-placement-new – Ben Voigt Dec 11 '10 at 18:29
  • @There: In the absence of user-defined `void* ::operator new(std::size_t, void*)`, yes that's correct. Oh I do wish C++ had a different syntax for construct-in-place placement new vs. bunch-of-extra-parameters placement new. – Ben Voigt Dec 11 '10 at 18:33
  • @Ben and why on earth can't there be just delete ptr (It is Q to the guys from std not to you)? Wouldn't that be less error prone if I wouldn't have to remember which ver of delete to use? In my opinion it should be automatically resolved, and I (a programmer) shouldn't be bothered by such a trivia. – There is nothing we can do Dec 11 '10 at 18:39
  • Ah-hah, user-defined `void* ::operator new(std::size_t, void*)` is prohibited in section `[new.delete.placement]` of the standard. – Ben Voigt Dec 11 '10 at 18:42
  • 1
    @There: There are different versions to allow the implementer the opportunity to provide appropriate optimizations in specific situations. – Martin York Dec 11 '10 at 18:47
  • @Ben it would help even more if you would care to give a section no: 5.3.4 That way you can make it easier for people who are not that familiar with std to be able to find this info quickly. – There is nothing we can do Dec 11 '10 at 18:50
  • @There: section `[new.delete.placement]` is section 18.6.1.3 in the C++0x FCD and draft n3225. I provide the names because the section numbers can change from revision to revision, and searching for the section names is easy enough (search with brackets and all). – Ben Voigt Dec 11 '10 at 19:14
  • @Ben in that case you should provide draft no and section no. – There is nothing we can do Dec 11 '10 at 19:36
  • "but your call doesn't call dtors of X." - In this particular case you can probably just omit this step, since the destructor does absolutely nothing. OTOH, the compiler doesn't seem to be very happy about calling `delete` on a void pointer. – UncleBens Dec 12 '10 at 00:34
  • @UncleBens: Yup, the code is once again wrong. Fixing. – Ben Voigt Dec 12 '10 at 01:44
  • Long ago I asked the same question http://stackoverflow.com/q/2499895/57428 – sharptooth Dec 13 '10 at 12:12

5 Answers5

7

These functions (operator new etc.) are not generally intended to be called explicitly, but rather used implicitly by new/new[] expressions (symmetrically, operator delete/operator delete[] functions are invoked implicitly by delete/delete[] expressions). An expression that uses new syntax for non-array type will implicitly call operator new function, while an expression with new[] will implicitly call operator new[].

The important detail here is that an array created by new[] expression will normally be destroyed later by delete[] expression. The latter will need to know the number of objects to destruct (if the objects have non-trivial destructors), i.e. this information has to be passed somehow from new[] expression (when it was known) to the corresponding delete[] expression (when it is needed). In a typical implementation this information is stored inside the block allocated by new[] expression, which is why the memory size requested in the implicit call to operator new[] is normally greater than the product of the number of elements and the element size. The extra space is used to store the household information (number of elements, namely). Later delete[] expression will retrieve that household information and use it to invoke the correct number of destructors before actually freeing the memory by calling operator delete[].

In your example you are not using any of these mechanisms. In your example you are calling memory allocation functions explicitly, perform construction manually and completely ignore the destruction step (which is OK, since your object has trivial destructor), which means that at least for destruction purposes you don't need to track the exact number of elements in the array. In any case, you keep track of that number manually, in a no variable.

However, in general case it is not possible. In general case the code will use new[] expressions and delete[] expressions and the number of elements will have to get from new[] to delete[] somehow, meaning that it has to be stored internally, which is why there's a need for a dedicated memory allocation function for arrays - operator new[]. It is not equivalent to a mere operator new with the aforementioned product as size.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • I would re-phrase the last paragraph. Even though the new/delete don't need the count (as it is always 1), they could have been implemented using the same technique as new[]/delete[] (thus removing the requirement that new be matched with delete and new[] is matched with delete[]). The reason for the separation (I believe) is to allow an optimized version of new/delete – Martin York Dec 11 '10 at 19:24
  • @Martin: For the global allocator and deallocator functions, the reason for the separation is almost certainly for debugging purposes, so the library can catch people mismatching the `new` and `delete` operators. e.g. `class X p = new X[10]; delete p;` can be caught with the help of allocation tracking in `::operator new[]`. Optimizations would more likely depend on the size of the block rather than whether there's a single object or array stored inside. – Ben Voigt Dec 12 '10 at 01:48
  • @Martin: I see it as a chicken-and-egg kind of problem: either `new` is a optimized version of `new[]` (`new[]` being "fundamental" functionality here), or `new[]` is an extended (for arrays) version of `new` (`new` being "fundamental" functionality in this case). I always preferred to see it in the latter way, with `new` being more fundamental than `new[]`. You seem to prefer the former one. Maybe you are actually right. – AnT stands with Russia Dec 12 '10 at 02:23
1

In your example code, you're using placement new to perform the construction that operator new[] performs automatically - with the difference that new[] will only perform default construction and you're performing a non-default placement construction.

The following is more or less equivalent to your example:

#include <iostream>

using namespace std;

struct X
{
    int data_;
    X(int v=0):data_(v){}
};

int main(int argc, char* argv[])
{
    unsigned no = 10;

    X* xp = new X[no];

    for (unsigned i = 0; i < no; ++i) {
        X tmp(i);
        xp[i] = tmp;
    }

    for (unsigned i = 0; i < no; ++i)
    {
        cout << (xp[i]).data_ << '\n';
    }

    delete[] xp;

    return 0;
}

The differences in this example are:

  • I believe the example here is more readable (casts can be ugly, and placement new is a pretty advanced technique that isn't often used, so isn't often understood)
  • it properly destroys the allocated objects (it doesn't matter in the example since the object are PODs, but in the general case, you need to call the dtor for each object in the array)
  • it has to default construct the array of objects, then iterate over them to set the actual value for the object - this is the one disadvantage in this example as opposed to yours

I think that in general, using new[]/delete[] is a much better than allocating raw memory and using placement new to construct the objects. It pushes the complexity of the bookkeeping into those operators instead of having it in your code. However, if the cost of the "default construct/set desired value" pair of operations is found to be too costly, then the complexity of doing it manually might be worthwhile. That should be a pretty rare situation.

Of course, any discussion of new[]/delete[] needs to mention that using new[]/'delete[]should probably be avoided in favor of usingstd::vector`.

Michael Burr
  • 333,147
  • 50
  • 533
  • 760
  • After some discussion in the comments, we learned that his question is about the `void* ::operator new(std::size_t)` function, not the `new` operator. – Ben Voigt Dec 11 '10 at 18:28
  • After reading the comments, I'm more confused than ever - the comments most seem to be discussing how to properly destroy objects created with placement `new`. Should I delete this answer? It still seems relevant to what's in the question (except that the question has been modified to destroy the `placed` objects), but you say I.m missing the point. – Michael Burr Dec 11 '10 at 18:39
  • The way I read the question, he's asking whether `void* ::operator new[](size_t)` is syntactic sugar for `::operator new(size_t)`, i.e. whether he can just call the scalar version directly and cut out the middle-man. And the answer is no, because `::operator new[]()` returns a pointer that can be passed (without UB) to `::operator delete[](void*)` and `::operator new()` doesn't. And btw I meant the first four comments following my answer, not the question, maybe that's the confusion. – Ben Voigt Dec 11 '10 at 19:48
  • @Ben and what is the difference between void* returned by operator new and void* returned by operator new[]? – There is nothing we can do Dec 11 '10 at 20:32
  • @There: Did you not read my comment? Passing the return value of `::operator new[]()` to `::operator delete[](void*)` is correct. Passing the return value of `::operator new()` to `::operator delete[](void*)` invokes undefined behavior. Just because two functions have the same signature does not mean they are the same. Clearly `sin(x)` is not the same function as `cos(x)`, even though they both have the same signature. – Ben Voigt Dec 12 '10 at 00:03
  • @Ben but return type of those two fncs (operator new and operator new[]) is identical so how is it possible that in one case it is ok and in another it causes UB? – There is nothing we can do Dec 12 '10 at 01:31
  • @There: Are you just dense? There is more to a value than its type. Consider: `int* p1 = new int; int* p2 = new int[10];`. Both `p1` and `p2` have the same *type*, but `p2[8] = 5;` is valid while `p1[8] = 5` is undefined behavior. `malloc` also returns `void*`. Do you think it's ok to call `::operator delete[](malloc(5))`? The C++ standard says it isn't. I could keep giving examples all year, but until you give up your preconceived notion that you're explaining why `new` and `new[]` are the same and stop and think about the possibility you could be wrong, more examples won't do any good – Ben Voigt Dec 12 '10 at 01:34
0

There's not a whole lot of difference between the required behavior of the functions void* operator new(size_t) and void* operator new[](size_t), except that they're paired with different deallocation functions.

The operators themselves are very different. One of the differences between the operators is which allocation function is used, but there are ultimately many other differences including how many constructors get called, etc. But your example code isn't using the operators (well, it is using placement new). You might want to change your question title to be clear about that.

From section [basic.stc.dynamic.deallocation]:

If a deallocation function terminates by throwing an exception, the behavior is undefined. The value of the first argument supplied to a deallocation function may be a null pointer value; if so, and if the deallocation function is one supplied in the standard library, the call has no effect. Otherwise, the value supplied to operator delete(void*) in the standard library shall be one of the values returned by a previous invocation of either operator new(std::size_t) or operator new(std::size_t, const std::nothrow_- t&) in the standard library, and the value supplied to operator delete[](void*) in the standard library shall be one of the values returned by a previous invocation of either operator new[](std::size_t) or operator new[](std::size_t, const std::nothrow_t&) in the standard library.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • @Ben why are you're saying that my example isn't using those operators? What about ::operator new(sizeof(X) * no)? Isn't that use of one of them? – There is nothing we can do Dec 11 '10 at 17:35
  • @Ben You are also saying that "The operators themselves are very different...". Could you please provide some links so I (and other users) could read about those differences? Thanks – There is nothing we can do Dec 11 '10 at 17:37
  • No, that's calling the global function named `operator new`. This code uses operator new: `xp = new P(0);`. – Ben Voigt Dec 11 '10 at 17:38
  • @Ben ok there was misunderstanding I see. What I would like to know is: Is there a difference between fncs: operator new and operator new[] or is the second ver. only a syntactic sugar to the first one? – There is nothing we can do Dec 11 '10 at 17:44
  • Section `[expr.new]` in the standard covers all the details. Things like, the allocation size can overflow when using array `new[]` but not with scalar `new`. – Ben Voigt Dec 11 '10 at 17:45
  • The functions both do the same thing: allocate a chunk of memory. Whether one calls the other depends on your runtime library, and is subject to change with user-defined versions of `operator new()` (either global or type-specific). The critical different between the functions is that `void* operator new(size_t)` returns a memory block that can be deallocated with `void operator delete(void*)`, while `void* operator new[](size_t)` returns a memory block that can be deallocated with `void operator delete[](void*)`. – Ben Voigt Dec 11 '10 at 17:48
  • @Ben so basically I'm not wrong in saying that the "indexed" ver of this fnc is just a syntactic sugar? – There is nothing we can do Dec 11 '10 at 17:48
  • @Ben to your last comment: (In VS) it is ultimately operator delete(void*) called. So it doesn't matter if you call delete vp; or delete[] vp. The same operator delete(void*) will be called for either of those calls. – There is nothing we can do Dec 11 '10 at 17:50
  • That would be wrong. They are separate functions, or at least the standard allows them to be. It's entirely possible that some compiler/library mix exists where one allocates from memory backed by swapfile, and the other one creates a file on disk and a memory mapped (to associate it with a valid pointer). More commonly, you'll find that in the debug version of a library, it will tag the allocated memory and check during deallocation to make sure the new and delete calls were properly matching. – Ben Voigt Dec 11 '10 at 17:52
  • @Ben the same goes for operator new and operator new[] - at the end the same fnc is called: operator new(size) (in VS). – There is nothing we can do Dec 11 '10 at 17:54
  • Are you talking about the functions or the operators now? It definitely does matter whether you call `delete vp;` or `delete[] vp;`. And while the functions may do the same thing in the library provided by a particular compiler vendor, mixing them up is illegal according to the standard. See the quote I added to my answer. – Ben Voigt Dec 11 '10 at 17:57
  • @Ben In this entire question I'm talking ONLY about fncs – There is nothing we can do Dec 11 '10 at 17:59
  • Well, in any case they aren't interchangeable, if you want your code to run correctly when built with compilers which are standard-compliant. – Ben Voigt Dec 11 '10 at 18:10
  • @Ben: His question seems to be not "are operator new and operator new[] distinct according to the standard?", but "*why* should the standard draw such a distinction between them?" In other words, what would have been the problem with `new T[n]` calling `operator new` and not `operator new[]` internally? Why does the standard specify that there should be a separate `operator new[]`? What is the *underlying* reason for it? – Stuart Golodetz Dec 11 '10 at 19:19
  • @Stuart that's freaking exactly what I mean! +1 – There is nothing we can do Dec 11 '10 at 19:38
  • @Stuart: Unfortunately, that's not the question that was asked. And I did provide some examples of what the rationale could have been, about 7 comments ago. – Ben Voigt Dec 11 '10 at 19:44
  • And it strikes me as a good question, given that in MSVC at least, `operator new[]` is implemented as `return (operator new(count));`. – Stuart Golodetz Dec 11 '10 at 19:44
  • @Ben: Oh well, that was the way I read it, anyway. Why would you want to allocate memory for individual objects and arrays from different pools, out of interest? – Stuart Golodetz Dec 11 '10 at 19:48
  • 1
    @Stuart: I don't have a perfect answer for that. I can think of a lot of reasons to allocate memory for differently-typed object (different sizes) from different pools, and I can see that any class that defines a custom operator new probably wouldn't want to allocate arrays from it (esp. considering the overhead allowed for arrays to store the number of destructors delete needs to call later), but right now I can't think of any reason for global individual objects and arrays to be allocated differently. Although consistency with the class-specific case is a good thing, IMO. – Ben Voigt Dec 11 '10 at 19:52
  • 1
    @Stuart: Here's another: It provides a way to detect many cases of mismatched `new`/`delete`, which is a very useful thing for a debug version of the allocator to do, because it catches real problems with destructors not getting called. – Ben Voigt Dec 12 '10 at 01:41
0

Allocation is one thing, object construction/destruction is another.

A new performs one allocation and one construction. However, a new[] still allocates one continuous memory block, but calls many constructors.

The situation is the same with delete and delete[].

BTW- I'm not 100% sure with what I'm about to say, but I believe that you won't get an imemdiate memory leak if you call delete on an address received from new[]. The whole memory block would probably be freed. However, this is invalid because you'd call the destructor on the first object only, instead of on every object in the array. And this may result in secondary memory leaks... and lots of logic errors due to broken 1-1 relation of constructors and destructors, of course.

(Also, remember to consider using boost::array or std::vector instead of new[]! :))

Kos
  • 70,399
  • 25
  • 169
  • 233
  • After some discussion in the comments, we learned that his question is about the `void* ::operator new(std::size_t)` function, not the `new` operator. – Ben Voigt Dec 11 '10 at 18:27
0

As user sankoz explains in his answer to in fact the same question, having separate operator new[] is intended for overloading single object allocations and array allocations separately in classes.

For example, if you have some specific class and you know that its instances are never larger than say 50 bytes you might want to overload operator new for that class so that its instances are allocated on a superfast allocator for blocks of size 50.

But what if the user calls new[]? The array can have whatever number of elemenents, so you can't universally allocate them on your custom allocator. The solution is you don't have to care of array allocations unless you want to.

Community
  • 1
  • 1
sharptooth
  • 167,383
  • 100
  • 513
  • 979