alloca and placement new[] weirdness

Started by
18 comments, last by Naurava kulkuri 15 years, 5 months ago
For the following code, if the Spam class has a destructor, running the code will result in run time check failure saying the stack around alloca is corrupted. Using the debugger revealed that the pointer returned from the placement new operator was not the same as void* p!!(4bytes offset, which I don't understand as the placement new merely returns the passed pointer!) My gut feeling is that it has something to do with the alignment or non-POD type, but alloca should be able to handle alignment issue and having a user-declared constructor should automatically disqualify Spam to be POD anyways, event if it does not, I don't understand why adding a non-virtual destructor can have such a huge difference. Can anyone shed some light on this mystery? Thank you in advance!!

struct Spam 
{
	Spam()
	{
		for (int i = 0; i < 10; i++)
		{
			values = 85;
		}
	}

	~Spam()
	{
		std::cout << "Byebye\n";
	}

	void print_me() const
	{
		for (int i = 0; i < 10; i++)
		{
			std::cout << values << " ";
		}

		std::cout << "\n";
	}

	int values[10];
};


int _tmain(int argc, _TCHAR* argv[])
{
	void* p = alloca(sizeof(Spam) * 5);

	Spam* spams = new (p) Spam[5];

	for (int i = 0; i < 5; i++)
	{
		spams.print_me();
	}

	return 0;
}

Advertisement
you placement newed 5 objects,
on m$ at least, implementation detail of new is that it stores the array size in an int before the pointer that it gives you. This is so that on delete[] it knows how to iterate through calling destructors.

If there is no destructor you give and the object doesnt need a default one i guess it avoids that extra counter. So thats your 4 byte discrepency.

not sure why you would want to alloca and then placement new tho, it all happens on the stack just liek normal local variables would,

M.
Thank you for the quick reply!!
Yeah, the first 4-byte is used to store the length of the array!

Hmmm so the effect of placement new[] depends on how the class/struct is declared, this is not good...
I guess I somehow stepped in to the realm of undefined behaviors.
This is a bit odd actually. That extra integer inserted by the compiler is normally used to figure out how many non-POD objects to destruct when delete [] is invoked, but since you're using placement new you should be destructing them manually anyway. I don't know what the has to standard say on the subject but I can't see the point of allowing array placement new in the first place unless it omits the count.

Anyway, why not simply use a loop to construct each object individually with a (non-array) placement new? That's essentially what the compiler is doing anyway.
Quote:Original post by Ventura
not sure why you would want to alloca and then placement new tho, it all happens on the stack just liek normal local variables would,
Presumably the idea is to be able to dynamically allocate a variable-length array of objects on the stack.
Quote:
That extra integer inserted by the compiler is normally used to figure out how many non-POD objects to destruct when delete [] is invoked, but since you're using placement new you should be destructing them manually anyway.


My guess is that when placement new fails(Exception thrown while constructing the objects) the compiler needs to call the corresponding placement delete to destruct the elements, at that point the compiler has to know the size of the array.

Quote:
Anyway, why not simply use a loop to construct each object individually with a (non-array) placement new? That's essentially what the compiler is doing anyway.


Yeah this seems to be a bettwr way, thanks.
I don't know about the current standard, but the draft of C++0x specifically says placement new returns the pointer it was passed.

If you're finding this is not the case, you can do the following:

void *p = ...;
Spam *s = reinterpret_cast<Spam *>(p);
new (p) Spam[5];

It requires implementation-defined behavior (reinterpret_cast) but it should work on most machines in use today.
"Walk not the trodden path, for it has borne it's burden." -John, Flying Monk
You could also write something like this.

Spam* p = static_cast<Spam*>(alloca(sizeof(Spam) * 5));for(int i=0; i<5; i++){    try    {        new(p+i) Spam;    }    catch(...)    {        for(int j=0; j<i; j++)        {            p[j].~Spam();        }        throw;    }}for(int i=0; i<5; i++)    p.print_me();for(int i=0; i<5; i++)    p.~Spam();


Since every constructor call can fail, you need to be careful to make the code exception-safe.

Ideally, you'd want to wrap everything up nicely in constructors/destructors, but it's not possible with alloca, which is the reason why people don't use it in C++.
If I'm not mistaken placement new calls destructors on already created instances all right (and doesn't deallocate the memory). It has no problem knowing how many there are and how much memory they occupy because that is all passed as arguments to it. So, if you go destroying the objects after a failed placement you may very well cause double destruction.
Quote:Original post by visitor
If I'm not mistaken placement new calls destructors on already created instances all right (and doesn't deallocate the memory). It has no problem knowing how many there are and how much memory they occupy because that is all passed as arguments to it. So, if you go destroying the objects after a failed placement you may very well cause double destruction.


You are mistaken :( (unless I misunderstand what you're saying).

If you use placement new to create an object in an array of chars (for example), then the destructor for that array will be called when its lifetime ends. Of course, the destructor for an array of chars is essentially a no-op. It doesn't know that placement new has put something special there.

Each placement new must have a matching manual destructor invocation.

I'm pretty sure there's a difference between placement new and placement new[], which is why people are getting confused by the Standard. :) Both forms say "I have enough memory to do the initialization, so do it already". In the case of an array-allocation, "enough" is apparently up for interpretation.

But yes, a manual destruction is necessary for things that are placement new'd.

struct Spam { /* as before */ };int main(int argc, char* argv[]) {	// static_cast might be sufficient?	Spam* spams = reinterpret_cast<Spam*>(alloca(sizeof(Spam) * 5));	for (int i = 0; i < 5; ++i) { assert (new (spams) Spam) == spams; }	for (int i = 0; i < 5; ++i) { spams.print_me(); }	for (int i = 0; i < 5; ++i) { spams.~Spam(); }	// return 0; is implicit for main	// alloca() requires no deallocation :)}

This topic is closed to new replies.

Advertisement