tags:

views:

2134

answers:

6

I've just done a test with bitfields, and the results are surprising me.

class test1 {
public:
    bool test_a:1;
    bool test_b:1;
    bool test_c:1;
    bool test_d:1;
    bool test_e:1;
    bool test_f:1;
    bool test_g:1;
    bool test_h:1;
};

class test2 {
public:
    int test_a:1;
    int test_b:1;
    int test_c:1;
    int test_d:1;
    int test_e:1;
    int test_f:1;
    int test_g:1;
    int test_h:1;
};

class test3 {
public:
    int test_a:1;
    bool test_b:1;
    int test_c:1;
    bool test_d:1;
    int test_e:1;
    bool test_f:1;
    int test_g:1;
    bool test_h:1;
};

The results were:-

sizeof(test1) = 1   // This is what I'd expect. 8 bits in a byte
sizeof(test2) = 4   // Reasonable. Maybe padded out to the size of an int.
sizeof(test3) = 16  // What???

Is this what you'd expect, or a compiler bug? (Codegear C++ Builder 2007, btw...)

+6  A: 

your compiler has arranged all of the members of test3 on integer size boundaries. Once a block has been used for a given type (integer bit-field, or boolean bit-field), the compiler does not allocate any further bit fields of a different type until the next boundary.

I doubt it is a bug. It probably has something to do with the underlying architecture of your system.

edit:

c++ compilers will allocate bit-fields in memory as follows: several consecutive bit-field members of the same type will be allocated sequentially. As soon as a new type needs to be allocated, it will be aligned with the beginning of the next logical memory block. The next logical block will depend on your processor. Some processors can align to 8-bit boundaries, while others can only align to 16-bit boundaries.

In your test3, each member is of a different type than the one before it, so the memory allocation will be 8 * (the minimum logical block size on your system). In your case, the minimum block size is two bytes (16-bit), so the size of test3 is 8*2 = 16.

On a system that can allocate 8-bit blocks, I would expect the size to be 8.

e.James
But, if that's the case, why 16, instead of 20 ((4 + 1) * 4) or 32 ((4 + 4) * 4)?
Chris Jester-Young
I'm guessing that your system cannot align to anything smaller than 16-bit boundaries. When test_a:1 is allocated, it takes up the first bit of a 16-bit field. When test_b:1 is allocated, it is of a different type, so the compiler starts it on the next 16-bit boundary, for a total of 128 bits.
e.James
A: 

No that is what I would have expected, since the order is significant. If you grouped the bools and ints you would get a very different result. As I remember bitfields only work on a common type.

kenny
+3  A: 

Wow, that's surprising. In GCC 4.2.4, the results are 1, 4, and 4, respectively, both in C and C++ modes. Here's the test program I used that works in both C99 and C++.

#ifndef __cplusplus
#include <stdbool.h>
#endif
#include <stdio.h>

struct test1 {
    bool test_a:1;
    bool test_b:1;
    bool test_c:1;
    bool test_d:1;
    bool test_e:1;
    bool test_f:1;
    bool test_g:1;
    bool test_h:1;
};

struct test2 {
    int test_a:1;
    int test_b:1;
    int test_c:1;
    int test_d:1;
    int test_e:1;
    int test_f:1;
    int test_g:1;
    int test_h:1;
};

struct test3 {
    int test_a:1;
    bool test_b:1;
    int test_c:1;
    bool test_d:1;
    int test_e:1;
    bool test_f:1;
    int test_g:1;
    bool test_h:1;
};

int
main()
{
    printf("%zu %zu %zu\n", sizeof (struct test1), sizeof (struct test2),
                            sizeof (struct test3));
    return 0;
}
Chris Jester-Young
try putting struct test3 { public: ....; }; since there is an access modifier before it, the compiler isn't allowed to reorder anymore. (there is a sentence in the standard stating that after such a thing, until the other one, reordering is not allowed)
Johannes Schaub - litb
hm. no, i've just tested it. that didnt make it larger :/
Johannes Schaub - litb
+2  A: 

As a general observation, a signed int of 1 bit doesn't make a lot of sense. Sure, you can probably figure out how to store 0 in it, but then the trouble starts.

One bit must be the sign-bit, even in two's complement, but you only have one bit to play with. So, if you allocate that as the sign-bit, you have no bits left for the actual value.

To me, this datatypes makes no sense.

Use unsigned int small : 1; to make it unsigned, then you can store the values 0 and 1 in a non-ambiguous manner.

unwind
hehe. If it's one's complement, it can only store plus and minus zero...I'd used int just as an example. My 'real' code when I hit this was using uints.
Roddy
If it's a two's-complement signed 1bit value, then a clear bit represents 0 and a set bit represents -1. Where's the problem? ;-)
Steve Jessop
+3  A: 

Be careful with bitfields as much of its behavior is implementation (compiler) defined:

From C++03, 9.6 Bitfields (pg. 163):

Allocation of bit-fields within a class object is implementation-defined. Alignment of bit-fields is implementation-defined. Bit-fields are packed into some addressable allocation unit. [Note:bit-fields straddle allocation units on some machines and not on others. Bit-fields are assigned right-to-left on some machines, left-to-right on others. ]

That is, it is not a bug in the compiler but rather lack of a standard definition of how it should behave.

David Rodríguez - dribeas
neat. thanks for digging it up.
Johannes Schaub - litb
A: 
#include <iostream>
using namespace std;

bool ary_bool4[10];

struct MyStruct {
    bool a1 :1;
    bool a2 :1;
    bool a3 :1;
    bool a4 :1;
    char b1 :2;
    char b2 :2;
    char b3 :2;
    char b4 :6;
    char c1;
};

int main() {
    cout << "char size:\t" << sizeof(char) << endl;
    cout << "short int size:\t" << sizeof(short int) << endl;
    cout << "default int size:\t" << sizeof(int) << endl;
    cout << "long int size:\t" << sizeof(long int) << endl;
    cout << "long long int size:\t" << sizeof(long long int) << endl;
    cout << "ary_bool4 size:\t" << sizeof(ary_bool4) << endl;
    cout << "MyStruct size:\t" << sizeof(MyStruct) << endl;
    // cout << "long long long int size:\t" << sizeof(long long long int) << endl;
    return 0;
}

char size: 1
short int size: 2
default int size: 4
long int size: 4
long long int size: 8
ary_bool4 size: 10
MyStruct size: 3
t1t0