please explain why this is Undefined Behavior

R

REH

Someone more articulate than me please explain to Ioannis Vranos why the
following two programs both exhibit undefined behavior:
 
V

Victor Bazarov

REH said:
Someone more articulate than me please explain to Ioannis Vranos why the
following two programs both exhibit undefined behavior:

What have you managed so far?
#include <iostream>
#include <cstring>

class SomeClass
{
public:

double d;
int i;
float f;
long l;
};


int main()
{
using namespace std;

unsigned char array[sizeof(SomeClass)];

SomeClass obj= {1, 2, 3, 4};

memcpy(array, &obj, sizeof(obj));

SomeClass *p= reinterpret_cast<SomeClass *>(array);

As I can see, 'obj' and 'array' have different alignment requirements.
Reinterpret-casting 'array' to 'obj' may violate hardware protocols for
loading values from multi-byte objects.
cout<<p->d<<" "<<p->i<<" "<<p->f<<" "<<p->l<<"\n";
}





#include <iostream>
#include <cstring>

class SomeClass
{
public:

double d;
int i;
float f;
long l;
};


int main()
{
using namespace std;

unsigned char array[sizeof(SomeClass)];

SomeClass obj= {1, 2, 3, 4};

SomeClass *p= new(array)SomeClass(obj);

Same thing here.

You could fix that if you went

unsigned char *array = new unsigned char[sizeof(SomeClass)];

and then as it was. 'new' returns pointers properly aligned.

V
 
P

Phlip

REH said:
Someone more articulate than me please explain to Ioannis Vranos why the
following two programs both exhibit undefined behavior:

I'm unsure of the more hairy end of the definition of "Plain Ole Data
Structure", but I feel safe calling that one.
int main()
{
using namespace std;

unsigned char array[sizeof(SomeClass)];

SomeClass obj= {1, 2, 3, 4};

memcpy(array, &obj, sizeof(obj));

SomeClass *p= reinterpret_cast<SomeClass *>(array);

Well-defined. reinterpret_cast is used on storage whose contents you know to
be the correct type.
cout<<p->d<<" "<<p->i<<" "<<p->f<<" "<<p->l<<"\n";
}
int main()
{
using namespace std;

unsigned char array[sizeof(SomeClass)];

SomeClass obj= {1, 2, 3, 4};

SomeClass *p= new(array)SomeClass(obj);

Well-defined. obj got copy-constructed into a new object with a valid type
and valid storage.

Down here, we need a matching ~delete. Right? Or does the POD make it not
needed? I would add it anyway and let the compiler throw it away.
 
R

REH

Victor Bazarov said:
What have you managed so far?

That you cannot arbitrarily cast a char* to any other pointer, because of
alignment issues. But, I am right aren't I? This is undefined behavior.
#include <iostream>
#include <cstring>

class SomeClass
{
public:

double d;
int i;
float f;
long l;
};


int main()
{
using namespace std;

unsigned char array[sizeof(SomeClass)];

SomeClass obj= {1, 2, 3, 4};

memcpy(array, &obj, sizeof(obj));

SomeClass *p= reinterpret_cast<SomeClass *>(array);

As I can see, 'obj' and 'array' have different alignment requirements.
Reinterpret-casting 'array' to 'obj' may violate hardware protocols for
loading values from multi-byte objects.
cout<<p->d<<" "<<p->i<<" "<<p->f<<" "<<p->l<<"\n";
}





#include <iostream>
#include <cstring>

class SomeClass
{
public:

double d;
int i;
float f;
long l;
};


int main()
{
using namespace std;

unsigned char array[sizeof(SomeClass)];

SomeClass obj= {1, 2, 3, 4};

SomeClass *p= new(array)SomeClass(obj);

Same thing here.

You could fix that if you went

unsigned char *array = new unsigned char[sizeof(SomeClass)];

and then as it was. 'new' returns pointers properly aligned.

V
 
?

=?ISO-8859-1?Q?Daniel_Sch=FCle?=

[...]
int main()
{
using namespace std;

unsigned char array[sizeof(SomeClass)];

SomeClass obj= {1, 2, 3, 4};

SomeClass *p= new(array)SomeClass(obj);


Same thing here.


You could fix that if you went

unsigned char *array = new unsigned char[sizeof(SomeClass)];

and then as it was. 'new' returns pointers properly aligned.

properly aligned regardless the different type, SomeClass here instead
of returned "new unsigned char[]"?

struct X
{
X(){}
~X(){}
};
void foo()
{
char buffer[1024];
X * x = new(buffer) X; // << undefined behaviour, because //
buffer resides on stack?
x->~X();
}
int main()
{
char * buffer = new char[1024];
X * x = new(buffer) X; // Ok? because ..
x->~X();
}

Did I got it right?

Thanks in advance

--Daniel
 
?

=?ISO-8859-1?Q?Daniel_Sch=FCle?=

[...]
int main()
{
char * buffer = new char[1024];
X * x = new(buffer) X; // Ok? because ..
x->~X();
}

s malenkoi oshibkoi :)

delete [] buffer;

--Daniel
 
I

Ioannis Vranos

Victor said:
You could fix that if you went

unsigned char *array = new unsigned char[sizeof(SomeClass)];

and then as it was. 'new' returns pointers properly aligned.


So are the following programs portable?

#include <iostream>
#include <cstring>

class SomeClass
{
public:

double d;
int i;
float f;
long l;
};


int main()
{
using namespace std;

unsigned char *parray= new unsigned char[sizeof(SomeClass)];

SomeClass obj= {1, 2, 3, 4};

memcpy(parray, &obj, sizeof(obj));

SomeClass *p= reinterpret_cast<SomeClass *>(parray);

//Continue using parray as SomeClass
cout<<p->d<<" "<<p->i<<" "<<p->f<<" "<<p->l<<"\n";

//Is this needed?
p->~SomeClass();

delete[] parray;
}




#include <iostream>
#include <cstring>

class SomeClass
{
public:

double d;
int i;
float f;
long l;
};


int main()
{
using namespace std;

unsigned char *parray= new unsigned char[sizeof(SomeClass)];

SomeClass obj= {1, 2, 3, 4};

SomeClass *p= new(parray)SomeClass(obj);

//Continue using parray as SomeClass
cout<<p->d<<" "<<p->i<<" "<<p->f<<" "<<p->l<<"\n";

//Is this needed?
p->~SomeClass();

delete[] parray;
}
 
R

REH

Ioannis Vranos said:
Victor said:
You could fix that if you went

unsigned char *array = new unsigned char[sizeof(SomeClass)];

and then as it was. 'new' returns pointers properly aligned.


So are the following programs portable?

#include <iostream>
#include <cstring>

class SomeClass
{
public:

double d;
int i;
float f;
long l;
};


int main()
{
using namespace std;

unsigned char *parray= new unsigned char[sizeof(SomeClass)];

SomeClass obj= {1, 2, 3, 4};

memcpy(parray, &obj, sizeof(obj));

SomeClass *p= reinterpret_cast<SomeClass *>(parray);

//Continue using parray as SomeClass
cout<<p->d<<" "<<p->i<<" "<<p->f<<" "<<p->l<<"\n";

//Is this needed?
p->~SomeClass();

delete[] parray;
}




#include <iostream>
#include <cstring>

class SomeClass
{
public:

double d;
int i;
float f;
long l;
};


int main()
{
using namespace std;

unsigned char *parray= new unsigned char[sizeof(SomeClass)];

SomeClass obj= {1, 2, 3, 4};

SomeClass *p= new(parray)SomeClass(obj);

//Continue using parray as SomeClass
cout<<p->d<<" "<<p->i<<" "<<p->f<<" "<<p->l<<"\n";

//Is this needed?
p->~SomeClass();

delete[] parray;
}

They are portable because new (and malloc, et. al.) guarantee the memory
returned will properly alignment for any type. I prefer to just union the
array with a variable of the type with the greatest alignment requirement in
the system, there by avoiding heap allocation altogether. No, I don't think
the calls to the destructor are needed in this case because the type used is
a POD, but they don't hurt and will avoid issues in the future if they type
ever changes. Also, if the type changes to a non-POD, the memcpy becomes
non-portable.

REH
 
V

Victor Bazarov

Daniel said:
[...]
void foo()
{
char buffer[1024];
X * x = new(buffer) X; // << undefined behaviour, because //
buffer resides on stack?

Not because on the stack but because there are no guarantees that
the alignment requirements are met.
x->~X();
}
int main()
{
char * buffer = new char[1024];
X * x = new(buffer) X; // Ok? because ..

Not OK. You had to obtain the 'buffer' pointer using sizeof(X).
x->~X();
}

Did I got it right?

Nope.

V
 
V

Victor Bazarov

Ioannis said:
Victor said:
You could fix that if you went

unsigned char *array = new unsigned char[sizeof(SomeClass)];

and then as it was. 'new' returns pointers properly aligned.


So are the following programs portable?

Strictly speaking, no, because they access members of the object
through a pointer that isn't a pointer to the original object.
'reinterpret_cast' is said to only preserve the object if the pointer
(and the object) on a "round-trip". This is safe:

struct SomeClass { int a; double b; char c; };

SomeClass obj = { 1,2,3 };
char *ptr = reinterpret_cast<char*>(obj); // cast to unrelated ptr
SomeClass *p = reinterpret_cast<SomeClass*>(ptr); // cast back
(p->a == 1) // yields 'true'

V
 
T

Thomas Matthews

REH said:
That you cannot arbitrarily cast a char* to any other pointer, because of
alignment issues. But, I am right aren't I? This is undefined behavior.

You can cast a pointer to any type. The results are implementation
(compiler) defined. I don't believe that it is undefined behavior.
Casting char * to other pointer types is commonly performed in the
embedded systems arena, but that portion of the code is platform
specific.

--
Thomas Matthews

C++ newsgroup welcome message:
http://www.slack.net/~shiva/welcome.txt
C++ Faq: http://www.parashift.com/c++-faq-lite
C Faq: http://www.eskimo.com/~scs/c-faq/top.html
alt.comp.lang.learn.c-c++ faq:
http://www.comeaucomputing.com/learn/faq/
Other sites:
http://www.josuttis.com -- C++ STL Library book
http://www.sgi.com/tech/stl -- Standard Template Library
 
R

REH

Strictly speaking, no, because they access members of the object
through a pointer that isn't a pointer to the original object.
'reinterpret_cast' is said to only preserve the object if the pointer
(and the object) on a "round-trip". This is safe:

struct SomeClass { int a; double b; char c; };

SomeClass obj = { 1,2,3 };
char *ptr = reinterpret_cast<char*>(obj); // cast to unrelated ptr
SomeClass *p = reinterpret_cast<SomeClass*>(ptr); // cast back
(p->a == 1) // yields 'true'

V
So, even correcting for the alignment, it's still not portable? Only when
your original pointer is of the correct type, portability is acheived? Is
there no way to "allocate" a block of non-heap memory, and construct an
object in it. I thought that was what placement new provided you, and then
the only concern was alignment.
 
R

REH

Thomas Matthews said:
behavior.

You can cast a pointer to any type. The results are implementation
(compiler) defined. I don't believe that it is undefined behavior.
Casting char * to other pointer types is commonly performed in the
embedded systems arena, but that portion of the code is platform
specific.
So, it is "implementation specific," not "undefined behavior." Thanks, I
will remember that.
 
?

=?ISO-8859-1?Q?Daniel_Sch=FCle?=

[..]
int main()
{
char * buffer = new char[1024];
X * x = new(buffer) X; // Ok? because ..


Not OK. You had to obtain the 'buffer' pointer using sizeof(X).

would multiple of sizeof(X) also be right?

thnanks

-Daniel
 
V

Victor Bazarov

REH said:
So, even correcting for the alignment, it's still not portable? Only when
your original pointer is of the correct type, portability is acheived? Is
there no way to "allocate" a block of non-heap memory, and construct an
object in it. I thought that was what placement new provided you, and then
the only concern was alignment.

You thought correctly. You can do

char *ptr = new char[sizeof(SomeClass)];

and then

SomeClass *p = new (ptr) SomeClass;

which will construct the object which will have the proper alignment.
The object will have to be later destroyed using the explicit d-tor
call syntax:

p->~SomeClass();

even if the d-tor is trivial or provided by the compiler.

What you shouldn't do is

char *ptr = new char[sizeof(SomeClass)];
SomeClass *p = reinterpret_cast<SomeClass*>(ptr);

at this point '*p' is not a properly constructed object. The result
of such conversion is unspecified and therefore not portable.

V
 
V

Victor Bazarov

Daniel said:
[..]
int main()
{
char * buffer = new char[1024];
X * x = new(buffer) X; // Ok? because ..



Not OK. You had to obtain the 'buffer' pointer using sizeof(X).


would multiple of sizeof(X) also be right?

thnanks

I would assume so. In your original example sizeof(X) _could_ be
larger than 1024, there was no indication otherwise.

V
 
R

REH

Victor Bazarov said:
REH said:
So, even correcting for the alignment, it's still not portable? Only when
your original pointer is of the correct type, portability is acheived? Is
there no way to "allocate" a block of non-heap memory, and construct an
object in it. I thought that was what placement new provided you, and then
the only concern was alignment.

You thought correctly. You can do

char *ptr = new char[sizeof(SomeClass)];

and then

SomeClass *p = new (ptr) SomeClass;

which will construct the object which will have the proper alignment.
The object will have to be later destroyed using the explicit d-tor
call syntax:

p->~SomeClass();

even if the d-tor is trivial or provided by the compiler.

What you shouldn't do is

char *ptr = new char[sizeof(SomeClass)];
SomeClass *p = reinterpret_cast<SomeClass*>(ptr);

at this point '*p' is not a properly constructed object. The result
of such conversion is unspecified and therefore not portable.

V

Ah, OK, I misunderstood you. I thought you were saying both were not
portable. Thanks for the clarification.

REH
 
I

Ioannis Vranos

Victor said:
You thought correctly. You can do

char *ptr = new char[sizeof(SomeClass)];

and then

SomeClass *p = new (ptr) SomeClass;

which will construct the object which will have the proper alignment.
The object will have to be later destroyed using the explicit d-tor
call syntax:

p->~SomeClass();

even if the d-tor is trivial or provided by the compiler.

What you shouldn't do is

char *ptr = new char[sizeof(SomeClass)];
SomeClass *p = reinterpret_cast<SomeClass*>(ptr);

at this point '*p' is not a properly constructed object. The result
of such conversion is unspecified and therefore not portable.


Very interesting stuff. However if you replace reinterpret_cast with:

SomeClass *p = static_cast<SomeClass *>( static_cast<void *>(ptr) );

is it guaranteed to always work?


Also, the standard says:

"For any object (other than a base-class subobject) of POD type T, whether or not the
object holds a valid value of type T, the underlying bytes (1.7) making up the object can
be copied into an array of char or unsigned char. If the content of the array of char or
unsigned char is copied back into the object, the object shall subsequently hold its
original value".


So in strict terms it looks like that only if we copy the bytes back, we get the same
object, and making an object in our bytes is not guaranteed to work.

The only examples I have seen in TC++PL3 for placement new, are in the style:


int x;

int *p= new(&x) int;
 
V

Victor Bazarov

Ioannis said:
Victor said:
You thought correctly. You can do

char *ptr = new char[sizeof(SomeClass)];

and then

SomeClass *p = new (ptr) SomeClass;

which will construct the object which will have the proper alignment.
The object will have to be later destroyed using the explicit d-tor
call syntax:

p->~SomeClass();

even if the d-tor is trivial or provided by the compiler.

What you shouldn't do is

char *ptr = new char[sizeof(SomeClass)];
SomeClass *p = reinterpret_cast<SomeClass*>(ptr);

at this point '*p' is not a properly constructed object. The result
of such conversion is unspecified and therefore not portable.



Very interesting stuff. However if you replace reinterpret_cast with:

SomeClass *p = static_cast<SomeClass *>( static_cast<void *>(ptr) );

is it guaranteed to always work?

Yes. The cast to void* is actually implicit, so static_cast is OK for
the inverse conversion (inversion). Which, of course, brings up a simple
question, why create such complication? Shouldn't it be allowed? BFD,
right? I don't know the answer beyond the fact that C used to do that
with its casts, and C++ tried to be safer and less error-prone in that
area.
Also, the standard says:

"For any object (other than a base-class subobject) of POD type T,
whether or not the object holds a valid value of type T, the underlying
bytes (1.7) making up the object can be copied into an array of char or
unsigned char. If the content of the array of char or unsigned char is
copied back into the object, the object shall subsequently hold its
original value".

IOW, if SomeClass is a POD, doing

SomeClass someobject;
char* temporary_storage = new char[sizeof(SomeClass)];
memcpy(temporary_storage, &someobject, sizeof(SomeClass));
SomeClass someotherobject;
memcpy(&someotherobject, temporary_storage, sizeof(SomeClass));

at this point 'someobject' and 'someotherobject' hold the same value.
So in strict terms it looks like that only if we copy the bytes back, we
get the same object, and making an object in our bytes is not guaranteed
to work.

While the Standard does appear to be limiting to copying /back/, the
intention is to probably provide a way to copy POD objects using char
or unsigned char arrays as the medium. I bet you can learn more on the
rationale and the particulars of the use of "the" in the aforementioned
paragraph of the Standard in comp.std.c++ :)
The only examples I have seen in TC++PL3 for placement new, are in the
style:


int x;

int *p= new(&x) int;

Examples are what they are, examples. It doesn't necessarily mean you
cannot use placement new with non-POD objects or for "copying".

V
 
I

Ioannis Vranos

Victor said:
Examples are what they are, examples. It doesn't necessarily mean you
cannot use placement new with non-POD objects or for "copying".


Is this also guaranteed to work? SomeClass is a non-POD type here:


#include <iostream>
#include <new>

class SomeClass
{
public:
SomeClass() { std::cout<<"Constructor called!\n"; }
~SomeClass() { std::cout<<"Destructor called!\n"; }

void somefunc() const { std::cout<<"somefunc() called!\n"; }
};


int main()
{
using namespace std;

unsigned char *parray= new unsigned char[sizeof(SomeClass)];

SomeClass *pSomeClass= new(parray) SomeClass;

pSomeClass->somefunc();

pSomeClass->~SomeClass();

delete[] parray;
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,537
Members
45,020
Latest member
GenesisGai

Latest Threads

Top