Filling a bit field in C

Continuing my experiments in C, I wanted to see how bit fields fit into memory. I am working on a 64-bit Intel machine. Here is my code snippet:

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> int main(int argc, char**argv){ struct box_props { unsigned int opaque : 1; unsigned int fill_color : 3; unsigned int : 4; unsigned int show_border : 1; unsigned int border_color : 3; unsigned int border_style : 2; unsigned int : 2; }; struct box_props s; memset(&s, 0, 32); s.opaque = 1; s.fill_color = 7; s.show_border = 1; s.border_color = 7; s.border_style = 3; int i; printf("sizeof box_porps: %d sizeof unsigned int: %d\n", sizeof(struct box_props), sizeof(unsigned int)); char *ptr = (char *)&s; for (i=0; i < sizeof(struct box_props); i++){ printf("%x = %x\n", ptr + i, *(ptr + i)); } return 0; 

and here is the result:

 sizeof box_porps: 4 sizeof unsigned int: 4 5be6e2f0 = f 5be6e2f1 = 3f 5be6e2f2 = 0 5be6e2f3 = 0 

And here's the question: why does struct box_props have a size of 4 - could it not be only 2 bytes? How is the addition done in this case? I'm a little confused (nomen. Omen).

thanks in advance for all answers

+4
source share
4 answers

Despite the fact that in this case the general requirement is only 2 bytes (1 + 3 + 4 + 1 + 3 + 2 + 2), the size of the data type used ( unsigned int ) is 4 bytes. Thus, the allocated memory is also 4 bytes. If you want to use only 2 bytes, use unsigned short as your data type and run the program again.

+9
source

From ISO C standard:

An implementation can allocate any addressable storage unit that is large enough to store a bit field. (and later) There may be an unnamed pad at the end of a structure or union.

Therefore, there is no requirement to always choose the smallest possible piece of memory for the structure. And since 32-bit words are probably their own size for your compiler, this is what it chooses.

+6
source

The placement of bit fields in memory depends not only on how the compiler decides to assign different fields in the structure, but also on the limbs of the machine you are working on. Let's take them one by one. The purpose of the fields inside the compiler can be controlled by specifying the size of the field (like @DDD), as well as through another mechanism. You can tell the compiler either pack about your structure, or leave it more suitable for how the compiler might want to optimize for the architecture of the machine for which you are compiling. Packaging is indicated using the packed attribute. Thus, if you specify the structure as:

 struct __attribute__ ((__packed__)) box_props { ... } 

you can see another layout in memory. Please note that you will not see that the layout is different from the analysis of structure components - it is a layout in memory that can change. Packing the structure is critical when communicating with something else, such as an I / O device that expects certain bits in certain places.

The second problem with bitfield structures is the dependence of their layout on endian-ness. The layout of the structure in memory (or any data for that matter) depends on whether you are running big-endian (POWER) or little-endian (e.g. x86) . Some systems (such as PowerPC embedded systems are bidirectional).

In the general case, bit fields make code porting very difficult since you are working with the layout of data in memory.

Hope this helps!

+2
source

For some reason, I donโ€™t understand, the developers of the C standard decided that specifying a number type together with a bit field should allocate enough space to store this number type if the previous field was not a bit field allocated from the same type that had enough free space to process the next field.

In your specific example, on a machine with 16-bit unsigned shorts, you should change the declarations in your bitfield to unsigned shorts. As it happens, unsigned char will also work and give the same results, but this is not always the case. If optimally packed bit fields will cover char boundaries, but not short borders, then to declare bit fields as unsigned char will need to fill in to avoid such a redistribution.

Although some processors will not have problems creating code for bit fields that limit the boundaries of storage blocks, this C standard would prohibit them from packing in this way (again, for reasons that I donโ€™t understand). For example, on a machine with typical 8/16/32/64-bit data types, the compiler could not allow the programmer to specify a 3-byte structure containing eight three-byte fields, since the fields would have to saddle the byte boundaries, I could understand the specification that does not require compilers to process fields that cross borders, or require that these bit fields be deferred in a certain way (I would consider them to be infinitely more useful if I could indicate that a particular bit field should, for example, use a bit 4- 7 in some place), but this standard seems to give the worst of both worlds.

In any case, the only way to effectively use bit fields is to find out where the storage boundaries are and select the types for the bit codes accordingly.

PS - It is interesting to note that although I recall the compilers used to prohibit volatile declarations for structures containing bit fields (since the sequence of operations when writing a bit field may not be defined), the semantics can be clearly defined in accordance with the new rules (I donโ€™t know whether the specification really requires them). For example, given:

 typedef struct { uint64_t b0:8,b1:8,b2:8,b3:8, b4:8,b5:8,b6:8,b7:8; uint64_t b8:8,b9:8,bA:8,bB:8, bC:8,bD:8,bE:8,bF:8; } FOO; extern volatile FOO bar; 

bar.b3 = 123; operator bar.b3 = 123; will read the first 64 bits of bar , and then write the first 64 bits of bar with the updated value. If bar were not volatile, the compiler could replace this sequence with a simple 8-bit record, but bar can be something like a hardware register that can only be written in 32-bit or 64-bit fragments.

If I had my druthers, it would be possible to define bit fields using something like:

 typedef struct { uint32_t { baudRate:13=0, dataFormat:3, enableRxStartInt: 1=28, enableRxDoneInt: 1, enableTxReadyInt: 1, enableTxEmptyInt: 1;}; }; } UART_CONTROL; 

indicates that baudRate is 13 bits starting at bit 0 (LSB), dataFormat is 3 bits starting at baudRate, enableRxStartInt is bit 28, etc. Such syntax will allow recording many types of data packing and unpacking in a portable mode and would allow performing many manipulations with I / O registers in the compiler-agnostic method (although such code would obviously be hardware specific).

+2
source

Source: https://habr.com/ru/post/1482167/


All Articles