2

The question: How to use "placement new" for creating an array with dynamic size? or more specifically, how to allocate memory for array elements from a pre-allocated memory.

I am using the following code:

void* void_array = malloc(sizeof(Int)); 
Int* final_array = new(void_array) Int;

This guarantees that the final_array* (the array pointer) is allocated from the place that is reserved by void_array*. But what about the final_array elements? I want them to be allocated from a pre-allocated memory as well.

P.S: I have to say that I'm using some API that gives me some controls over a tile architecture. There is a function that works exactly like malloc, but also have other features, e.g. lets you control the properties of the allocated memory. So, what i basically need to do, is to use that malloc-like function to allocate memory with my desired properties (e.g. from which memory bank, to be cached where and etc.)

towi_parallelism
  • 1,421
  • 1
  • 16
  • 38
  • 5
    This is already wrong. If you're going to placement-new an `Int` somewhere, that somewhere needs to be allocated `sizeof(Int)` space, not `sizeof(Int*)`. Also, look up a custom allocator + `std::vector<>` or `std::list<>`. – GManNickG Nov 06 '12 at 14:54
  • It makes no sense to allocate a non-fixed size. When you allocate memory, you need to tell the allocator how much you need. – Gorpik Nov 06 '12 at 15:11
  • @Gorpic: It makes sense in my application. I'm using an API which gives me some memory management over the hardware (where to allocate the memory from is more important for me rather than how much) – towi_parallelism Nov 06 '12 at 15:14
  • This is like going to a hotel and saying "I need rooms next month". There is no way they can guarantee they'll have them unless I tell them how many I need. Besides, in this case I'm saying "I need rooms with correlative numbers", because an array memory block must be contiguous. – Gorpik Nov 06 '12 at 15:22
  • @Gorpik: I wish I could explain the whole model here. But the research is about asking one memory bank to allocate the memory you need. If it couldn't, it will automatically send your request to another memory bank. But the important part is that most of the time it has the memory you need, and you as a programmer if the system have separated your request traffic. – towi_parallelism Nov 06 '12 at 15:37
  • *Most of the time* is one of the nastiest sources of bugs in programmes around the world. Anyway, it is difficult to answer a question about custom memory management if we don't know how is memory managed. – Gorpik Nov 06 '12 at 15:50

2 Answers2

6

First off, let's make sure we all agree on the separation of memory allocation and object construction. With that in mind, let's assume we have enough memory for an array of objects:

void * mem = std::malloc(sizeof(Foo) * N);

Now, you cannot use placement array-new, because it is broken. The correct thing to do is construct each element separately:

for (std::size_t i = 0; i != N; ++i)
{
    new (static_cast<Foo*>(mem) + i) Foo;
}

(The cast is only needed for the pointer arithmetic. The actual pointer required by placement-new is just a void pointer.)

This is exactly how the standard library containers work, by the way, and how the standard library allocators are designed. The point is that you already know the number of elments, because you used it in the initial memory allocation. Therefore, you have no need for the magic provided by C++ array-new, which is all about storing the array size somewhere and calling constructors and destructors.

Destruction works in reverse:

for (std::size_t i = 0; i != N; ++i)
{
    (static_cast<Foo*>(mem) + i)->~Foo();
}

std::free(mem);

One more thing you must know about, though: Exception safety. The above code is in fact not correct unless Foo has a no-throwing constructor. To code it correctly, you must also store an unwind location:

std::size_t cur = 0;
try
{
    for (std::size_t i = 0; i != N; ++i, ++cur)
    {
        new (static_cast<Foo*>(mem) + i) Foo;
    }
}
catch (...)
{
    for (std::size_t i = 0; i != cur; ++i)
    {
        (static_cast<Foo*>(mem) + i)->~Foo();
    }
    throw;
}
Community
  • 1
  • 1
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Why placement new for arrays needs extra memory? This makes no sense - since we shall never call `delete[]` on that array. I understand that for "normal" new this overhead is needed to store e.g. an array size - but why this overhead is needed for arrays created with placement new? – PiotrNycz Nov 06 '12 at 15:02
  • @PiotrNycz: The reasons are involved and boring and require studying the text of the standard. Read the adherent question for details. (I agree that it makes no sense! It's a defect in the standard so boring nobody has bothered to submit a request for a fix.) – Kerrek SB Nov 06 '12 at 15:03
  • @Kerrek: Thanks a lot. So, do you agree that it's a good idea to use vectors for such situations? Or the same thin happens there as well? I mean is there any way that without need to allocate the space for each element, I do it once at first it applies to all other? – towi_parallelism Nov 06 '12 at 15:11
  • @TOWI_Parallelism: `std::vector` works more or less exactly the way I describe in my answer, so yes, do use that and don't re-invent the wheel. I don't understand your last question. – Kerrek SB Nov 06 '12 at 15:29
  • @TOWI_Parallelism You're basically reinventing the vector; this is exactly what `std::vector` has to do internally. If if you don't want any reallocations, either create the vector large enough to begin with (which would duplicate what is happening here), or use `reserve`. – James Kanze Nov 06 '12 at 15:29
  • To all: To make it more clear, I have to say that I'm using some API that gives me some controls over a tile architecture. There is a function that works exactly like malloc, but also have other properties. e.g. it lets you control the properties of the allocated memory. So, what i basically need to do, is to use that malloc-like function to allocates memory with my desired properties (e.g. from which memory bank, to be cached where and etc.) – towi_parallelism Nov 06 '12 at 15:44
  • @TOWI_Parallelism: That sounds like you might be able to write a suitable standard-compliant allocator and use that with a standard vector... – Kerrek SB Nov 06 '12 at 16:08
  • @Kerrek: If we consider a fixed size, why is this wrong? `void* void_array = malloc(sizeof(Int)*N); Int* final_array = new(void_array) Int[N];` – towi_parallelism Nov 06 '12 at 16:27
  • 2
    @TOWI_Parallelism: Read the [linked question](http://stackoverflow.com/q/8720425/596781). The details are technical and boring, and too long for this comment. Just take my word for it. Trust me, I'm a fish. (Or come ask in the chat room!) – Kerrek SB Nov 06 '12 at 16:30
1

Instead of using a custom malloc, you should overwrite operator new() and use it. This is not operator new; there is a function actually called operator new(), confusing as it may seem, which is the function used by the normal (non-placement) operator new in order to get raw memory upon which to construct objects. Of course, you only need to overwrite it if you need special memory management; otherwise the default version works fine.

The way to use it is as follows, asuming your array size will be size:

Int* final_array = static_cast<Int*>(size == 0 ? 0 : operator new(sizeof(Int) * size));

Then you can construct and destroy each element independently. For instance, for element n:

// Create
new(final_array + n) Int; // use whatever constructor you want

// Destroy
(final_array + n)->~Int();
Gorpik
  • 10,940
  • 4
  • 36
  • 56
  • 1
    Replacing `operator new` is a nice idea for this, but it won't help if he's concerned about allocations in a library written in C. And while there's no guarantee in the standard that the standard `operator new` will use `malloc`, I've never seen a case where it didn't. – James Kanze Nov 06 '12 at 15:31
  • 1
    @JamesKanze: I was trying to go *the C++ way* as much as possible with my answer. If C is involved, then forget about `operator new()`, of course. – Gorpik Nov 06 '12 at 15:36