After diving more into the topic, i've found that multiple bit fields aren't useful without bitwise operators and valid constructors, with that it depends a lot on the operating system.
The answer is tested on cygwin ( -Wno-unused-variable -O0 -ggdb flags ) on windows 7
Version 1: union
This is basic implementation without any bit fields, most common implementation of 4 byte colour space.
#include <iostream>
union c_space32{
uint32_t space;
uint8_t channels[4];
};
int main(){
{ // just a anonymous scope to keep things clear
union c_space32 temp = {0xff00fe32};
std::cout << "sizeof : " << sizeof( union c_space32 ) << "\n\n";
std::cout << (int)temp.channels[1] << "\t" << std::hex << temp.space << "\n";
++temp.channels[1];
std::cout << (int)temp.channels[1] << "\t" << std::hex << temp.space << "\n";
++temp.channels[1];
std::cout << (int)temp.channels[1] << "\t" << std::hex << temp.space << "\n";
}
return 0;}
The union behaves as normal color space, and every uint8_t
part of the union behaves as unique byte, so overall change of value in c_space32.channels
doesn't affect the value of c_space32.space
outside of the scope of the byte. This is the output i am getting.
sizeof : 4
fe ff00fe32
ff ff00ff32
0 ff000032
Version 2: bit-fields
The issue with bit fields ( among lack of documentation in some cases ), is that they can easily change in size, and that endianness depends on OS so native logic behind structure of bit fields can escape our human logic. Let me give you some examples for future guys/gals who wish to endeavour into this topic.
#include <iostream>
#include <bitset>
struct temp1{
uint8_t a:1;
temp1(uint8_t val){ // just so i can assign it
this->a = (val & 0x1 ); // this is needed to avoid truncated warning
}
};
int main(){
struct temp1 t1 = 3;
uint8_t *ptr = (uint8_t *)&t1;
std::cout << sizeof(struct temp1) << std::endl; // size of 1 byte
std::cout << std::bitset<8>( *ptr ) << std::endl; // 0000-0001 position of our bitfield
return 0;}
So in this case sizeof(struct temp1)
returns an size of 1 byte
. With the position of our bit field as upmost right. And here is where documentation starts to go MIA.
#include <iostream>
#include <bitset>
struct temp2{
uint8_t a:1;
uint8_t b:1;
uint8_t c:1;
temp2(int VAL){ // just so i can assign it
this->a = (VAL & 0x1 );
this->b = 0;
this->c = (VAL >> 2 ) & 0x1;
}
};
int main(){
struct temp2 t1 = 0xf;
uint8_t *ptr = (uint8_t *)&t1;
std::cout << sizeof(struct temp2) << std::endl; // size of 1
std::cout << std::bitset<8>( *ptr ) << std::endl; // 0000-0101
return 0;}
In this case constructor
is a must have, since computer doesn't know how you want to structure the data. Sure in our logic, if we line up bits it is logical that assigning them would be same as they are sharing the memory. But the issue is computer will not do bitwise operators
for us. Sure those bits are in order and lined up naturally ,but the computer just grabs some bit and define it as an unique variable, what you choose to place in that variable it is up to you.
If we were to exceed the scope of unit memory size
(byte), OS starts interfering in our work.
#include <iostream>
#include <bitset>
struct temp3{
bool b0:1;
bool b1:1;
bool b2:1;
bool b3:1;
bool b4:1;
bool b5:1;
bool b6:1;
bool b7:1;
temp3( int a ){
this->b0 = ( a & 0x1 );
this->b1 = ( a & 0x2 );
this->b2 = ( a & 0x4 );
this->b3 = ( a & 0x8 );
this->b4 = ( a & 0x10 );
this->b5 = ( a & 0x20 );
this->b6 = ( a & 0x40 );
this->b7 = ( a & 0x80 );
}
};
int main(){
struct temp3 t1 = 0xc3;
uint8_t *ptr = (uint8_t *)&t1;
std::cout << sizeof(struct temp3) << std::endl; // still size of 1
std::cout << std::bitset<8>( *ptr ) << std::endl; // 1100-0011
return 0;}
And when we exceed the byte size:
#include <iostream>
#include <bitset>
struct temp4{
bool b0:1;
bool b1:1;
bool b2:1;
bool b3:1;
bool b4:1;
bool b5:1;
bool b6:1;
bool b7:1;
bool b8:1;
temp4( int a ){
this->b0 = ( a & 0x1 );
this->b1 = ( a & 0x2 );
this->b2 = ( a & 0x4 );
this->b3 = ( a & 0x8 );
this->b4 = ( a & 0x10 );
this->b5 = ( a & 0x20 );
this->b6 = ( a & 0x40 );
this->b7 = ( a & 0x80 );
this->b8 = ( a & 0x100 );
}
};
int main(){
struct temp4 t1 = 0x1c3;
uint16_t *ptr = (uint16_t *)&t1;
std::cout << sizeof(struct temp4) << std::endl; // size of 2
std::cout << std::bitset<16>( *ptr ) << std::endl; // 0000-0000 1100-0011
std::cout << t1.b8 << std::endl; // still returns 1
std::cout << "\n\n";
union t_as{
uint16_t space;
temp4 data;
uint8_t bytes[2];
};
union t_as t2 = {0x1c3};
//11000011-00000001
std::cout << std::bitset<8>( t2.bytes[0] ) << "-" << std::bitset<8>( t2.bytes[1] ) << std::endl;
return 0;}
What happened here? Since we added another bool
bit-field
our struct grew for 1 byte ( since bool is 1 byte ), and our 16 bit pointer doesn't show the last b8
- but the union does. The issue is that OS took over, and in this case stuck the last bit behind our original memory - due to innate OS endianness. As you can see in the union , the byte is still read, but the order is different.
So when exceeding the byte size, normal OS rules apply.
CONCLUSION and ANSWER
struct half_opacity{
uint8_t alpha:4;
uint8_t red;
uint8_t green;
uint8_t blue;
half_opacity(int a){
this->alpha = ( a >> 24 )&0xf;
this->red = ( a >> 16 )&0xff;
this->green = ( a >> 8 )&0xff;
this->blue = ( a & 0xff );
}
operator uint32_t(){
return ( this->alpha << 24 )
| ( this->red << 16 )
| ( this->green << 8 )
| this->blue;
}
};
{
struct half_opacity c_space = 0xff00AABB;
std::cout << "size of : " << sizeof(struct half_opacity) << std::endl; //size of : 4
std::cout << std::hex << (uint32_t)c_space << std::endl; // 0x0f00AABB
}
So unless you plan to confide the original channel to some bit size, i would strongly suggest using union
approach, since there isn't any added benefit into splitting the 32 bit integer into individual bytes with bit-fields
. The major thing about bit fields
is that you need to split them and build then back up as with any other integer field - bit shifts
often circumvent the whole OS endianness thing.
The truncation warning you got, was due to multiple members in your struct, and struct naturally assigning the first one , and since you added more than the bit field
could handle the compiler warned you that some data will be lost.