Structures (also called structs) are a way to group several related variables into one place. Each variable in the structure is known as a member of the structure. Unlike an array, a structure can contain many different data types (int, float, char, etc.).
An optional identifier, called a "tag," gives the name of the structure type and can be used in subsequent references to the structure type. A variable of that structure type holds the entire sequence defined by that type. Structures in C are similar to the types known as "records" in other languages.
The general syntax for a struct declaration in C is: struct tag_name { type member1; type member2; /* declare as many members as desired, but the entire structure size must be known to the compiler.
The struct keyword defines a structure type followed by an identifier (name of the structure). Then inside the curly braces, you can declare one or more members (declare variables inside curly braces) of that structure. For example: struct Person { char name[50]; int age; float salary; };
Bitfields, carried over from C. Name
is 40 bits wide, Colour
is 24 bits wide. Your struct therefore has at least 64 bits. On my system 64 bits would be 8 bytes.
Here sizeof
nicely demonstrates what's going on under the hood:
#include <iostream>
#include <climits>
struct bc_1 {
int a : 1;
int b : 1;
};
struct bc_2 {
int a : 31;
int b : 1;
};
struct bc_3 {
int a : 32;
int b : 1;
};
struct bc_4 {
int a : 31;
int b : 2;
};
struct bc_5 {
int a : 32;
int b : 32;
};
struct bc_6 {
int a : 40;
int b : 32;
};
struct bc_7 {
int a : 63;
int b : 1;
};
int main(int argc, char * argv[]) {
std::cout << "CHAR_BIT = " << CHAR_BIT;
std::cout << " => sizeof(int) = " << sizeof(int) << std::endl;
std::cout << "1, 1: " << sizeof(struct bc_1) << std::endl;
std::cout << "31, 1: " << sizeof(struct bc_2) << std::endl;
std::cout << "32, 1: " << sizeof(struct bc_3) << std::endl;
std::cout << "31, 2: " << sizeof(struct bc_4) << std::endl;
std::cout << "32, 32: " << sizeof(struct bc_5) << std::endl;
std::cout << "40, 32: " << sizeof(struct bc_6) << std::endl;
std::cout << "63, 1: " << sizeof(struct bc_7) << std::endl;
}
What follows depends on your compiler and OS, and possibly on your hardware. On macOS with gcc-7 (with a CHAR_BIT
= 8, a 32 bit int
(i.e. half of 64 bit long
) has sizeof(int)
= 4) this is the output I see:
CHAR_BIT = 8 => sizeof(int) = 4
1, 1: 4
31, 1: 4
32, 1: 8
31, 2: 8
32, 32: 8
40, 32: 12
63, 1: 8
This tells us several things: if both fields of int
type fit into a single int
(i.e. 32 bits in the example above), the compiler allocates only a single int
's worth of memory (bc_1
and bc_2
). Once, a single int
can't hold the bitfields anymore, we add a second one (bc_3
and bc_4
). Note that bc_5
is at capacity.
Interestingly, we can "select" more bits than allowed. See bc_6
. Here g++-7 gives a warning:
bitfields.cpp::30:13: warning: width of 'bc_6::a' exceeds its type
int a : 40;
^~
Note that: clang++ explains this in better detail
bitfields.cpp:30:9: warning: width of bit-field 'a' (40 bits) exceeds the width of its type; value will be truncated to 32 bits [-Wbitfield-width]
int a : 40;
^
However it seems that under the hood, the compiler allocates another int
's worth of memory. Or at the very least, it determines the correct size. I guess the compiler is warning us not to access this memory as int a = bc_6::a
(I would wager that int a
would then only have the first 32 bits of field bc_6::a
...). This is confirmed by bc_7
whose total size is that of two int
s, but the first field covers most of them.
Finally replacing int
with long
in the example above behaves as expected:
CHAR_BIT = 8 => sizeof(long) = 8
1, 1: 8
31, 1: 8
32, 1: 8
31, 2: 8
32, 32: 8
40, 32: 16
63, 1: 8
Yes, that is the syntax for bitfields. They are commonly used to define structs that map onto hardware registers. There are some things to keep in mind if you decide to use them, one is that you can't know how the compiler does the layout, ordering and padding in the actual bytes making up the fields can and will differ among compilers (and perhaps with the same compiler but with different optimization settings, too).
That's a bitfield definition.
Name is an integer that's able to store exactly 40 bits of information. Colour can store 24 bits.
This is often done to save some space in often needed structures, or compress code down to a size that's easy to handle for the CPU (in your case 64 bits. Fit's exactly into a CPU register on a 64 bit machine).
The code that accesses the bitfields will execute a tad slower though.
Use them judiciously:
Remember that almost everything about bit fields is implementation dependent. For example, whether bits are stored left-to-right or right-to-left depends on the actual hardware architecture. Furthermore, each compiler uses a different member alignment model, which is why the size of the optimized BillingRec is 12 bytes rather than 9. You cannot take a bit field's address nor can you create an arrays of bits. Finally, on most implementations the use of bit fields incurs speed overhead. Therefore, when you optimize your code, measure the effect of a certain optimization and its tradeoffs before you decide to use it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With