chenlv Product Page
If you want to learn more, please visit our website chenlv.
In C, we can specify the size (in bits) of the structure and union members. The idea of bit-field is to use memory efficiently when we know that the value of a field or group of fields will never exceed a limit or is within a small range. C Bit fields are used when the storage of our program is limited.
Need of Bit Fields in C
- Reduces memory consumption.
- To make our program more efficient and flexible.
- Easy to Implement.
Bit-fields are variables that are defined using a predefined width or size. Format and the declaration of the bit-fields in C are shown below:
struct { data_type member_name : width_of_bit-field; };
where,
In this example, we compare the size difference between the structure that does not specify bit fields and the structure that has specified bit fields.
Consider the following declaration of date without the use of bit fields.
#include <stdio.h>
struct
date {
unsigned
int
d;
unsigned
int
m;
unsigned
int
y;
};
int
main()
{
printf
(
"Size of date is %lu bytes\n"
,
sizeof
(
struct
date));
struct
date dt = { 31, 12, 2014 };
printf
(
"Date is %d/%d/%d"
, dt.d, dt.m, dt.y);
}
Size of date is 12 bytes Date is 31/12/2014
The above representation of ‘date’ takes 12 bytes on a compiler whereas an unsigned int takes 4 bytes. Since we know that the value of d is always from 1 to 31, and the value of m is from 1 to 12, we can optimize the space using bit fields.
The below code defines a structure named date with a single member month. The month member is declared as a bit field with 4 bits.
struct date { // month has value between 0 and 15, // so 4 bits are sufficient for month variable. int month : 4; };
However, if the same code is written using signed int and the value of the fields goes beyond the bits allocated to the variable, something interesting can happen.
Below is the same code but with signed integers:
#include <stdio.h>
struct
date {
int
d : 5;
int
m : 4;
int
y;
};
int
main()
{
printf
(
"Size of date is %lu bytes\n"
,
sizeof
(
struct
date));
struct
date dt = { 31, 12, 2014 };
printf
(
"Date is %d/%d/%d"
, dt.d, dt.m, dt.y);
return
0;
}
Size of date is 8 bytes Date is -1/-4/2014
Explanation
The output comes out to be negative. What happened behind is that the value 31 was stored in 5 bit signed integer which is equal to 11111. The MSB is a 1, so it’s a negative number and you need to calculate the 2’s complement of the binary number to get its actual value which is what is done internally.
By calculating 2’s complement you will arrive at the value 00001 which is equivalent to the decimal number 1 and since it was a negative number you get a -1. A similar thing happens to 12 in which case you get a 4-bit representation as 1100 and on calculating 2’s complement you get the value of -4.
Example: The below code demonstrates how to force alignment to the next memory boundary using bit fields.
#include <stdio.h>
struct
test1 {
unsigned
int
x : 5;
unsigned
int
y : 8;
};
struct
test2 {
unsigned
int
x : 5;
unsigned
int
: 0;
unsigned
int
y : 8;
};
int
main()
{
printf
(
"Size of test1 is %lu bytes\n"
,
sizeof
(
struct
test1));
printf
(
"Size of test2 is %lu bytes\n"
,
sizeof
(
struct
test2));
return
0;
}
Size of test1 is 4 bytes Size of test2 is 8 bytes
Example: The below code demonstrates that taking the address of a bit field member directly is not allowed.
#include <stdio.h>
struct
test {
unsigned
int
x : 5;
unsigned
int
y : 5;
unsigned
int
z;
};
int
main()
{
struct
test t;
printf
(
"Address of t.x is %p"
, &t.x);
printf
(
"Address of t.z is %p"
, &t.z);
return
0;
}
Output
prog.c: In function 'main': prog.c:14:1: error: cannot take address of bit-field 'x' printf("Address of t.x is %p", &t.x); ^
Example: The below code demonstrates the usage of bit fields within a structure and assigns an out-of-range value to one of the bit field members.
#include <stdio.h>
struct
test {
unsigned
int
x : 2;
unsigned
int
y : 2;
unsigned
int
z : 2;
};
int
main()
{
struct
test t;
t.x = 5;
printf
(
"%d"
, t.x);
return
0;
}
Output
Implementation-Dependent
Example: The below C program defines an array of bit fields and fails in the compilation.
#include <stdio.h>
struct
test {
unsigned
int
x[10] : 5;
};
int
main() {}
Output
prog.c:3:1: error: bit-field 'x' has invalid type unsigned int x[10]: 5; ^
Ans:
C
#include <stdio.h>
struct
test {
unsigned
int
x;
unsigned
int
y : 33;
unsigned
int
z;
};
int
main()
{
printf
(
"%lu"
,
sizeof
(
struct
test));
return
0;
}
Error:./3ec6d9b7-7ae2-411a-a60e-66992a7fc29b.c:4:5: error: width of 'y' exceeds its type unsigned int y : 33; ^
Ans:
C
#include <stdio.h>
struct
test {
unsigned
int
x;
long
int
y : 33;
unsigned
int
z;
};
int
main()
{
struct
test t;
unsigned
int
* ptr1 = &t.x;
unsigned
int
* ptr2 = &t.z;
printf
(
"%d"
, ptr2 - ptr1);
return
0;
}
Output4
Ans:
C
#include <stdio.h>
union
test {
unsigned
int
x : 3;
unsigned
int
y : 3;
int
z;
};
int
main()
{
union
test t;
t.x = 5;
t.y = 4;
t.z = 1;
printf
(
"t.x = %d, t.y = %d, t.z = %d"
, t.x, t.y, t.z);
return
0;
}
Outputt.x = 1, t.y = 1, t.z = 1
Last Updated :
08 Jun, 2023
Like Article
Save Article
Share your thoughts in the comments
Login
to comment...
$\begingroup$
Packing data is possible, both to the compiler and the user. An extreme example of this are u1
ints, a.k.a, booleans (as one bit unsigned integer has only two possible representations).
So if your language is focused or has an option to optimize to size (code or memory), this can be relevant. You can possibly pack 8 sequential booleans in a octec, instead of 8 sequential int_fast_t
(the performante choice) that in some machines will be wasteful as 8x64 = 512 bits.
It will be slow. To the point that the users complain about it.
Modern languages gives integers that are power of two, multiple of octets because modern CPUs have them. And because CPUs have then, they are fast. As in hardware fast. As in there is nothing more fast than this is possible fast. And users like that.
You may encounter situations where your compiler (or someone compilers) do the packing sometimes, but sometimes not. The problem with packed data is that CPUs may don't have how to write on packed data in an atomic way. The CPU will need to load the whole word, bitfidle it to simulate the partial write, and write back the whole memory world.
So, sometimes an a.b = true;
in one thread, a.c = false;
in another, in a packed struct, will result in impossible to reason results, another name for undefined behaviour.