Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is the "empty base optimization" in GCC configurable?

Tags:

c++

gcc

Consider these types:

  struct A {};
  struct B : A { int i; };

sizeof(A) > 0 as required by the standard.

sizeof(B) should be 4 due to the empty base optimization. Yet on GCC 4.1.1 it's 5 (I'm using a pack of 1 in this area). And inconsistently - some of my files are getting it, some are not. Can't be sure what the differences are yet, we have a large prjoect.

On the other three compilers I'm using (by Microsoft and Freescale), I don't have this problem. The empty base optimization is optional apparently, according to this article.

Is there a compiler option or pragma to tune this in GCC 4.1.1? I can work around the issue but I would like to understand what's going on first. I Googled for a while and can't seem to find anything.

like image 435
scobi Avatar asked Feb 13 '09 19:02

scobi


2 Answers

This always happens. I post immediately before I figure it out. Maybe the act of posting gets me thinking in a different way..

So in my question the sample was a little bit over-simplified. It's actually more like this:

struct Base {};
struct C1 : Base { int i; }
struct C2 : Base { C1 c; int i; }

sizeof(C1) is correctly 4 on all platforms, but sizeof(C2) is 9 instead of 8 on GCC. And... apparently GCC is the only thing that gets it right, according to the last bit of the article I linked to in the original question. I'll quote it (from Nathan Meyers) here:

A whole family of related "empty subobject" optimizations are possible, subject to the ABI specifications a compiler must observe. (Jason Merrill pointed some of these out to me, years back.) For example, consider three struct members of (empty) types A, B, and C, and a fourth non-empty. They may, conformingly, all occupy the same address, as long as they don't have any bases in common with one another or with the containing class. A common gotcha in practice is to have the first (or only) member of a class derived from the same empty base as the class. The compiler has to insert padding so that they two subobjects have different addresses. This actually occurs in iterator adapters that have an interator member, both derived from std::iterator. An incautiously-implemented standard std::reverse_iterator might exhibit this problem.

So, the inconsistency I was seeing was only in cases where I had the above pattern. Every other place I was deriving from an empty struct was ok.

Easy enough to work around. Thanks all for the comments and answers.

like image 149
scobi Avatar answered Nov 14 '22 03:11

scobi


GCC C++ follows these rules with standard padding:

NOTE: __attribute__((__packed__)) or changing the default packing will modify these rules.

  • class EmptyBase {}; --> sizeof(EmptyBase) == 1

  • Any number of empty-bases will map to 0 in the struct offset as long as all are unique types (including parenting).

  • Non empty-base parents are simply in the order declared with only padding for alignment.

  • If the first member of a derived class that immediately follows empty-bases does not derive from any of those bases, it is allowed to start at the first properly aligned offset for that member that is greater-than-or-equal-to the empty-base address -- this may be the same address as the empty-bases.

  • If the first member of a derived class that immediately follows empty-bases does derive from any of those bases, it will start at the first properly aligned offset for that member that is greater-than the empty-base address -- this is never the same address as the empty-bases.

  • Members that are empty-classes take at least one byte of storage in the containing class.


MSVC++ follows these rules:

NOTE: #pragma pack or changing the default packing will modify these rules.

  • class EmptyBase {}; --> sizeof(EmptyBase) == 1

  • The only way an empty-base class (or class derived from an empty-base) will start at offset 0 (zero) is if it is the first base class.

  • A Non-empty-base class will start at the next valid alignment offset for the base class.

  • All empty-base classes will appear to have zero effective storage in the derived class and do not affect the current offset unless followed by another empty-base class (or class derived from an empty-base) in which case you should see the following rule.

  • An empty-base class (or class derived from an empty-base) that follows an empty-base class (or class derived from an empty-base) will add 1 to the current offset position before padding to the proper alignment for the class.

  • There is no padding (other than for alignment) between the last base class and the first class member or vft-pointer(s). *** NOTE: this is an over-aggressive empty-base-optimization that can break the C++ standard.

  • Members that are empty-classes take at least one byte of storage in the containing class.

like image 3
Adisak Avatar answered Nov 14 '22 04:11

Adisak