Topic: sizeof/alignment issues wrt slicing assignment


Author: AlbertoBarbati@libero.it (Alberto Barbati)
Date: Mon, 29 Nov 2004 17:56:38 GMT
Raw View
Anders J. Munch wrote:
> "Alberto Barbati" <AlbertoBarbati@libero.it> wrote:
>=20
>>I believe this is non-portable code, because of footnote 70, unless you
>>re-define A::operator=3D.
>=20
> I think not.  None of this code involves the sizeof operator or such,
> so footnote 70 is a non-issue.

Frankly, I am not sure it is either one way of the other. Let me share=20
my doubt.

Fact is that a compiler-generated copy assignment is required to perform=20
the assigment memberwise, but the standard does not specify whether the=20
padding is also copied or not, as the padding is an implementation=20
detail. Sometimes it might be even more efficient to copy the padding=20
also! As I could not find any specification in merit, I assume the=20
decision is implementation-dependent. If that's true, for PODs and all=20
types with trivial assignment according to =A712.8/11 it might be=20
reasonable to implement the compiler-generated assignment as something=20
like memcpy(dst, src, sizeof(T)).

Question for the gurus: is my interpretation correct, thus far?

However, when a POD is used as a base, things change, because the=20
padding might be used to accept extra members, so when I copy the=20
padding while assigning the base, I could potentially overwrite=20
legitimate members in the derived class. This could be disastrous, for=20
example in this case:

struct Base  // Base has trivial ctor/dtor/assignment
{
   // let's assume that double has a large aligment, so there's
   // a lot of padding after c and that padding is suitable
   // enough to hold Derived::p
   double d;
   char c;
};

struct Derived : Base  // Derived has non-trivial ctor/dtor/assignment
{
   char* p; // holds dynamically allocated memory

   // ctors, dtors etc. omitted for brevity

   Derived& operator=3D(const Derived& d);
};

Derived& Derived::operator=3D(const Derived& d)
{
   if(this !=3D &d)
   {
     Base::operator=3D(d); // might overwrite this->p !?!?
     // ...
   }
}

So it seems that the compiler-generated assignment of Base *should not*=20
copy the padding.

Which interpretation is correct?

> Suppose we write the compiler-generated A::operator=3D out in full,
> assigning subobjects one by one as specified in 12.8/13:
>=20
> <snip>
>=20
> Now where is the undefined behaviour?

There's none, in this case. In fact I wrote "*unless* you re-define=20
A::operator=3D" in my previous message. The issue concerns only the=20
compiler-generated assignement operator.

Alberto

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: AlbertoBarbati@libero.it (Alberto Barbati)
Date: Thu, 25 Nov 2004 17:28:35 GMT
Raw View
J Scott Peter XXXIII i/iii wrote:
> Should sizeof(Type) be rounded up based on the alignment of the type?
> If so, what about types derived from Type? Can the new members be
> placed directly after sizeof(Type) without first rounding up to the
> alignment of Type?

=A75.3.3/2 is very precise on this matter:

"[...] When applied to a class, the result is the number of bytes in an=20
object of that class including any padding required for placing objects=20
of that type in an array. [...] The result of applying sizeof to a base=20
class subobject is the size of the base class type. [...]"

The second part is perhaps a bit obscure but footnote 70 provides more=20
info: "The actual size of a base class subobject may be less than the=20
result of applying sizeof to the subobject, due to virtual base classes=20
and less strict padding requirements on base class subobjects."

> This issue is critical for slicing assignment, as
> the following example shows:
>=20
> struct A {
>     __m128   Vec4;       // Intel SSE type which compiler aligns to 16
> bytes.
>     float    MiscA;
> };                       // __alignof(A) =3D=3D 16, sizeof(A) =3D=3D 20=
 or 32?
>=20

[note for non VC++ user: sizeof(__m128) =3D=3D __alignof(__m128) =3D=3D 1=
6]

sizeof(A) shall be 32, because it must take into account the padding.=20
VC++ reports this value correctly.

> struct B : A {
>     float MiscB;
> };                       // __alignof(B) =3D=3D 16, sizeof(B) =3D=3D 24=
 or 32?

sizeof(B) is not specified by the standard. For sure it can't be 24 as=20
sizeof(A) =3D=3D 32. sizeof(B) might be 32, because the extra float might=
=20
fit in the padding. This is one of the cases where footnote 70 kicks in.

In fact, on VC++, sizeof(B) depends on the #pragma pack in use. If the=20
packing is 1, 2 or 4, then sizeof(B) =3D=3D 32, otherwise sizeof(B) =3D=3D=
 48.=20
Notice that the default packing is 8.

> void SliceAssign(B& b, A const& a)
> {
>     static_cast<A&>(b) =3D a;
> }
>=20
> The purpose of SliceAssign is to set just the A component of b to the
> value of a. Note that the compiler generates the copy operator for A.

I believe this is non-portable code, because of footnote 70, unless you=20
re-define A::operator=3D.

> In Visual Studio 2003, the object code for SliceAssign appears
> incorrect: it performs a memcpy of 32 bytes, which seems to imply that
> the compiler believes sizeof(A) =3D 32.  However, the compiler places
> the member B::MiscB at offset 20, not 32, so SliceAssign overwrites
> MiscB.

IMHO, the compiler is correct in moving 32 bytes because sizeof(A) =3D=3D=
=20
32. The fact that the base class subobject might have a smaller size=20
cannot be taken into account here as the static_cast has destroyed the=20
information that the object was indeed a subobject of some larger object.

Please notice that if packing is 8 or 16, the offset of B::MiscB is 32=20
and not 20.

> The compiler has inconsistent ideas about the sizeof(A). It uses one
> value for whole objects (and arrays), and a smaller value when A is
> the base class of another class. Is this correct behavior?

The compiler is consistent, IMHO. Base class subobjects have less strict=20
requirements than their corresponding types and this difference is=20
clearly sanctioned by the standard.

HTH,

Alberto

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: andersjm@inbound.dk ("Anders J. Munch")
Date: Sat, 27 Nov 2004 23:55:51 GMT
Raw View
"Bob Hairgrove" <invalid@bigfoot.com> wrote:
>
> Alignment requirements are not covered by the C++ standard, AFAIK, but
> there might be a proposal in the works. I couldn't find anything in
> the standard about slicing objects. It usually leads to memory leaks
> and undefined behavior, though. But it would be nice to have a
> reference.

Slicing is just an informal term for (unexpectedly) invoking the
assignment operator of a base class.  From the point of view of the
C++ abstract machine, there is nothing undefined about that.  It just
tends to wreck havoc with subclass invariants.

In a correct implementation there is no particular interaction between
alignment and slicing.

- Anders


---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: andersjm@inbound.dk ("Anders J. Munch")
Date: Sat, 27 Nov 2004 23:55:22 GMT
Raw View
J Scott Peter XXXIII i/iii gave the example:
> > struct A {
> >     __m128   Vec4; // Intel SSE type which compiler aligns to 16
> > bytes.
> >     float    MiscA;
> > };
> > struct B : A {
> >     float MiscB;
> > };
> > void SliceAssign(B& b, A const& a)
> > {
> >     static_cast<A&>(b) = a;
> > }

"Alberto Barbati" <AlbertoBarbati@libero.it> wrote:
> I believe this is non-portable code, because of footnote 70, unless you
> re-define A::operator=.

I think not.  None of this code involves the sizeof operator or such,
so footnote 70 is a non-issue.

Suppose we write the compiler-generated A::operator= out in full,
assigning subobjects one by one as specified in 12.8/13:

A& A::operator=(A const& other)
{
  Vec4 = other.Vec4;
  MiscA = other.MiscA;
  return *this;
}

Now where is the undefined behaviour?  Line by line this looks fine to
me, and the static_cast in SliceAssign B does produce a reference to a
valid A object.

>
> > In Visual Studio 2003, the object code for SliceAssign appears
> > incorrect: it performs a memcpy of 32 bytes, which seems to imply that
> > the compiler believes sizeof(A) = 32.  However, the compiler places
> > the member B::MiscB at offset 20, not 32, so SliceAssign overwrites
> > MiscB.

Compiler bug.

<pedantic mode>
Provided that you can reproduce it with standard types, as the
standard places no restrictions on what happens when you use
implementation-reserved names (such as __m128).
</pedantic mode>

- Anders


---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: spidlunty@yahoo.com (J Scott Peter XXXIII i/iii)
Date: Thu, 25 Nov 2004 01:39:50 GMT
Raw View
Should sizeof(Type) be rounded up based on the alignment of the type?
If so, what about types derived from Type? Can the new members be
placed directly after sizeof(Type) without first rounding up to the
alignment of Type? This issue is critical for slicing assignment, as
the following example shows:

struct A {
    __m128   Vec4;       // Intel SSE type which compiler aligns to 16
bytes.
    float    MiscA;
};                       // __alignof(A) == 16, sizeof(A) == 20 or 32?

struct B : A {
    float MiscB;
};                       // __alignof(B) == 16, sizeof(B) == 24 or 32?

void SliceAssign(B& b, A const& a)
{
    static_cast<A&>(b) = a;
}

The purpose of SliceAssign is to set just the A component of b to the
value of a. Note that the compiler generates the copy operator for A.

In Visual Studio 2003, the object code for SliceAssign appears
incorrect: it performs a memcpy of 32 bytes, which seems to imply that
the compiler believes sizeof(A) = 32.  However, the compiler places
the member B::MiscB at offset 20, not 32, so SliceAssign overwrites
MiscB.

The compiler has inconsistent ideas about the sizeof(A). It uses one
value for whole objects (and arrays), and a smaller value when A is
the base class of another class. Is this correct behavior?

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: invalid@bigfoot.com (Bob Hairgrove)
Date: Thu, 25 Nov 2004 08:01:37 GMT
Raw View
On Thu, 25 Nov 2004 01:39:50 GMT, spidlunty@yahoo.com (J Scott Peter
XXXIII i/iii) wrote:

>Should sizeof(Type) be rounded up based on the alignment of the type?
>If so, what about types derived from Type? Can the new members be
>placed directly after sizeof(Type) without first rounding up to the
>alignment of Type? This issue is critical for slicing assignment, as
>the following example shows:
>
>struct A {
>    __m128   Vec4;       // Intel SSE type which compiler aligns to 16
>bytes.
>    float    MiscA;
>};                       // __alignof(A) == 16, sizeof(A) == 20 or 32?

32 seems correct here; not knowing sizeof(__m128), it's hard to tell.

>struct B : A {
>    float MiscB;
>};                       // __alignof(B) == 16, sizeof(B) == 24 or 32?

Also 32 ... the extra float member will probably fit within the extra
bytes reserved by the alignment requirements after the A member, since
alignment is 16, not 32.

>void SliceAssign(B& b, A const& a)
>{
>    static_cast<A&>(b) = a;
>}
>
>The purpose of SliceAssign is to set just the A component of b to the
>value of a. Note that the compiler generates the copy operator for A.

Why not assign the members explicitly? Is there a reason for doing it
this way?

>In Visual Studio 2003, the object code for SliceAssign appears
>incorrect: it performs a memcpy of 32 bytes, which seems to imply that
>the compiler believes sizeof(A) = 32.  However, the compiler places
>the member B::MiscB at offset 20, not 32, so SliceAssign overwrites
>MiscB.
>
>The compiler has inconsistent ideas about the sizeof(A). It uses one
>value for whole objects (and arrays), and a smaller value when A is
>the base class of another class. Is this correct behavior?

Alignment requirements are not covered by the C++ standard, AFAIK, but
there might be a proposal in the works. I couldn't find anything in
the standard about slicing objects. It usually leads to memory leaks
and undefined behavior, though. But it would be nice to have a
reference.

--
Bob Hairgrove
NoSpamPlease@Home.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]