3

I have a problem with bitfields in derived classes.

With the g++ compiler, you can assign __attribute__((packed)) to a class and it will pack bitfields. So

class A
{
  public:
    int one:10;
    int two:10;
    int three:10;
} __attribute__ ((__packed__));

takes up only 4 bytes. So far, so good.
However, if you inherit a class, like this

class B
{
  public:
    int one:10;
    int two:10;
} __attribute__ ((__packed__));

class C : public B
{
  public:
    int three:10;
} __attribute__ ((__packed__));

I would expect class C, which has the same content as class A above, to have the same layout as well, i.e. take up 4 bytes. However, C turns out to occupy 5 bytes.

So my question is, am I doing something wrong, and if so, what? Or is this a problem with the compiler? An oversight, a real bug?

I tried googling, but haven't really come up with anything, apart from a difference between Linux and Windows (where the compiler tries to emulate MSVC), which I'm not interested in. This is just on Linux.

Mr Lister
  • 45,515
  • 15
  • 108
  • 150
  • I’d guess that this is simply not possible but let’s see what others say. – Konrad Rudolph Jul 25 '12 at 09:39
  • Suppose you have `C c; B &b1 = c; /* ... */ B b2; /* ... */ b1 = b2;`. With your suggested packing, that last assignment becomes a bit more complicated. –  Jul 25 '12 at 09:41
  • @hvd How so? Assignments to bit members need bit operations anyway, so why not also for assignment to the class … – Konrad Rudolph Jul 25 '12 at 09:46
  • @hvd Why? Assignments to bitfields are always complicated; there's always bit shuffling going on. I don't see why this should be different. – Mr Lister Jul 25 '12 at 09:47
  • But `b1 = b2;` doesn't refer to bit-fields, it copies a whole class. It would be a very special class, where that class is POD, yet copying the class doesn't simply copy bytes. –  Jul 25 '12 at 09:58

2 Answers2

0

I believe the problem is with B, which cannot easily be 2.5 bytes. It has to be at least 3 bytes.

Theoretically, the derived class might be allowed to reuse padding from the base class, but I have never seen that happen.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
  • Somehow I still expected it to happen. To all intents and purposes, A and C have the same functionality. – Mr Lister Jul 25 '12 at 09:53
  • I wonder what happens to the leftover bits. Does the compiler automatically enlarge one of the fields to fill a whole byte? I'll have to test that. – Mr Lister Jul 25 '12 at 09:54
  • The left over bits are just left over, not used. If you form an array of B, `B b[10];`, sizeof(B) must be a whole number (otherwise you cannot get the address of `b[1]` and `b[2]`). When creating a C, the compiler *might* be allowed to use this left over space in B, but this is likely not a common enough use case for the compiler writers to bother. They have a lot of other things to worry about, with a potentially bigger payoff (like new C++11 features :-). – Bo Persson Jul 25 '12 at 11:20
  • Well, my use case is that I need to create a pretty big array of the things and I was hoping to save the bytes. Oh well. – Mr Lister Jul 25 '12 at 11:23
0

Imagine for a second that what you are asking for is possible. What would be possible side-effects or issues of that? Let's see on a particular example that you have. Also assume a 32-bit architecture with 1-byte memory alignment.

There are 20 consecutive bits in class A that you can address via class's members one and two. It's a very convenient addressing for you, human. But what does the compiler do to make it happen? It uses masks and bit shifts to position those bits into correct places.

So far so good, seems simple and safe enough.

Adding 10 more bits. Let's say there was some amazingly smart compiler that allows you to squeeze those extra 10 bits into an already used 32-bit word (they fit nicely, don't they?).

Here comes trouble:

A* derived = new B; // upcast to base class
derived->one = 1;
derived->two = 2;
// what is the value of derived->three in this context?
// Especially taking into account that a compiler is free to do all sorts
// of optimizations when generating code for class A

Because of the above the class has to use different and separately-addressable memory locations for members of class A and members of class B causing those 10 bits to "spill" into next addressable memory location - next byte.

Even more trouble comes when you consider multiple inheritance - what is the one true way of arranging the bits in a derived class?

YePhIcK
  • 5,816
  • 2
  • 27
  • 52
  • 1
    Multiple inheritance aside, the compiler is already as amazingly smart as that. Your example would work; that is, the unused bits in the parent class remain untouched when you assign something. And why not? There is already some ANDing and ORing going on which you can't optimise away; using "sloppy" bitmasks won't help improve performance. – Mr Lister Jul 25 '12 at 10:40