Sign in to follow this  
Classless

alloca and placement new[] weirdness

Recommended Posts

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[i] = 85;
		}
	}

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

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

		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[i].print_me();
	}

	return 0;
}

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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[i].print_me();

for(int i=0; i<5; i++)
p[i].~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++.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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[i]) Spam) == spams[i]; }
for (int i = 0; i < 5; ++i) { spams[i].print_me(); }
for (int i = 0; i < 5; ++i) { spams[i].~Spam(); }
// return 0; is implicit for main
// alloca() requires no deallocation :)
}

Share this post


Link to post
Share on other sites
Zahlman, the code you wrote is exactly the same as mine except you used reinterpret_cast where only static_cast is required, you made the code exception-unsafe, and on top of everything made it bogus and non-compilable.

It's not
assert (new (spams[i]) Spam) == spams[i];

but
Spam* tmp = new (&spams[i]) Spam;
assert(tmp == &spams[i]);

Share this post


Link to post
Share on other sites
Quote:
Original post by the_edd

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.


Yes. But I meant if an exception is thrown by a constructor, new[] should be able to destruct the successfully created instances and rethrow the exception. If it wasn't like that there would be no way to tell how many objects you need to destruct which would render new[] practically useless (as the code above my post indicated).

Share this post


Link to post
Share on other sites
Quote:
Original post by loufoque
Zahlman, the code you wrote is exactly the same as mine except you used reinterpret_cast where only static_cast is required, you made the code exception-unsafe, and on top of everything made it bogus and non-compilable.

It's not
assert (new (spams[i]) Spam) == spams[i];

but
Spam* tmp = new (&spams[i]) Spam;
assert(tmp == &spams[i]);


Er, didn't read your post. And of course the addresses are wrong and assert is a function or macro (so there should be parentheses around the whole thing), but it should still be doable on one line. (Also, '&spams[i]' should be writable as 'spams + i'.)

Blah. I guess the short version is, never mind me. :)

Share this post


Link to post
Share on other sites
Quote:
Original post by Zahlman
"{ assert (new (spams[i]) Spam) == spams[i]; }"
--
And of course the addresses are wrong and assert is a function or macro (so there should be parentheses around the whole thing)


The parentheses should be done by the macro-writer, this is not the problem. The real problem with that line is, that when _NDEBUG / NDEBUG is defined before inclusion of the assert-header, then your whole compound becomes


{ ((void) 0) == spams[i]; }




Even without NDEBUG, it could be

{ (0 != (new (spams[i]) Spam)) == spams[i]; }


edit: My failure this time. Of course the non-NDEBUG assert will probably emit some if-statement or inline-assembly, so compilation with NDEBUG may work, but yields undefined behaviour, and without NDEBUG will probably always fail. I hate bugs, i love them!


Which is not what you intended, I guess.

And afaik, assert() is supposed to be of type void, which drives both of my statements to undefined behaviour nevertheless.


edit: fixed typo

Share this post


Link to post
Share on other sites
But if I do put parentheses around the whole thing, then the whole thing will be interpreted as the macro argument, and then the statement just becomes '((void)0);', right? :/

Share this post


Link to post
Share on other sites
Quote:
Original post by Zahlman
But if I do put parentheses around the whole thing, then the whole thing will be interpreted as the macro argument, and then the statement just becomes '((void)0);', right? :/


"whole thing" could be anything :D
But yes, it would be "((void)0);" then, a nop ;)

Share this post


Link to post
Share on other sites
Raymond Chen, the man who knows the ins-and-outs of Windows systems and compilers, blogs about this very issue in hist post Mismatching scalar and vector new and delete. Indeed, reinterpret_cast should work for now, as noted by Extrarius. Be sure to check the comments as well as the answers.

[Edited by - Naurava kulkuri on November 17, 2008 1:16:31 PM]

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this