Layout structure memory with bit fields

I have this C struct: (representing an IP datagram)

struct ip_dgram { unsigned int ver : 4; unsigned int hlen : 4; unsigned int stype : 8; unsigned int tlen : 16; unsigned int fid : 16; unsigned int flags : 3; unsigned int foff : 13; unsigned int ttl : 8; unsigned int pcol : 8; unsigned int chksm : 16; unsigned int src : 32; unsigned int des : 32; unsigned char opt[40]; }; 

I assign values ​​to it, and then print its memory layout in 16-bit words as follows:

 //prints 16 bits at a time void print_dgram(struct ip_dgram dgram) { unsigned short int* ptr = (unsigned short int*)&dgram; int i,j; //print only 10 words for(i=0 ; i<10 ; i++) { for(j=15 ; j>=0 ; j--) { if( (*ptr) & (1<<j) ) printf("1"); else printf("0"); if(j%8==0)printf(" "); } ptr++; printf("\n"); } } int main() { struct ip_dgram dgram; dgram.ver = 4; dgram.hlen = 5; dgram.stype = 0; dgram.tlen = 28; dgram.fid = 1; dgram.flags = 0; dgram.foff = 0; dgram.ttl = 4; dgram.pcol = 17; dgram.chksm = 0; dgram.src = (unsigned int)htonl(inet_addr("10.12.14.5")); dgram.des = (unsigned int)htonl(inet_addr("12.6.7.9")); print_dgram(dgram); return 0; } 

I get this output:

 00000000 01010100 00000000 00011100 00000000 00000001 00000000 00000000 00010001 00000100 00000000 00000000 00001110 00000101 00001010 00001100 00000111 00001001 00001100 00000110 

But I expect this:

enter image description here

The output is partially correct; somewhere, bytes and nibbles seem to be interchangeable. Is there any problem in this issue? Are bit fields suitable for this purpose? I really do not know. Any help? Thanks in advance!

+7
source share
5 answers

No, bit fields are not suitable for this purpose. The layout is compiler dependent.

As a rule, it is not recommended to use bit fields for data in which you want to control the resulting layout if you do not have (for a specific compiler), for example #pragma s, for this.

The best way is probably to implement this without bit fields, i.e. by performing the necessary bitwise operations. This is annoying, but easier than somehow digging out a way to fix it. In addition, it is platform independent.

Define the header as an array of 16-bit words, and then you can easily calculate the checksum.

+7
source

Although the question was asked a long time ago, there is no answer explaining your result. I will answer it, I hope it will be useful to someone.

I will illustrate the error using the first 16 bits of your data structure.

Please note: this explanation is guaranteed only with the set of your processor and compiler. Any of these changes may change behavior.

Fields:

 unsigned int ver : 4; unsigned int hlen : 4; unsigned int stype : 8; 

Assigned:

 dgram.ver = 4; dgram.hlen = 5; dgram.stype = 0; 

The compiler begins to assign bit fields, starting at offset 0. This means that the first byte of your data structure is stored in memory as:

 Bit offset: 7 4 0 ------------- | 5 | 4 | ------------- 

The first 16 bits after assignment are as follows:

 Bit offset: 15 12 8 4 0 ------------------------- | 5 | 4 | 0 | 0 | ------------------------- Memory Address: 100 101 

You use an Unsigned 16 pointer for the dereference memory address 100. As a result, address 100 is treated as the low-order bit of a 16-bit number. And 101 is seen as an MSB 16-bit number.

If you type * ptr in hexadecimal, you will see the following:

 *ptr = 0x0054 

Your loop runs on this 16-bit value and therefore you get:

 00000000 0101 0100 -------- ---- ---- 0 5 4 

Solution: Reorder items to

 unsigned int hlen : 4; unsigned int ver : 4; unsigned int stype : 8; 

And use an unsigned char * pointer to move and print values. It should work.

Note that, as others have said, this behavior is platform and compiler specific. If any of these changes you need to verify the layout of your data structure.

+1
source

The C11 standard says:

An implementation can allocate any addressable storage unit large enough to hold a bitfield. If there is enough space left, a bit field that immediately follows another bit field in the structure should be packed into adjacent bits of the same block. If there is not enough space, will there be a bit field that does not fit, is placed in the next block or the overlap of adjacent blocks is determined by the implementation. The recipient of something distribution of bit fields within the unit (from high to low or from low to high order) is determined by the implementation.

I am sure that this is undesirable, as this means that there may be indents between the fields and you cannot control the order of your fields. Not only that, but you are on a whim of the implementation in terms of network byte order. Also, imagine if unsigned int only 16 bits, and you ask to insert a 32-bit bit field into it:

The expression defining the width of the bit field must be an integer constant expression with a non-negative value that does not exceed the width of the object of the type to be specified, a colon and the expression are omitted.

I suggest using an unsigned char array instead of a structure. Thus, you are guaranteed control over the addition and order of the network byte. Start with the size in bits you want your structure to be overall. I assume that you declare this in a constant, for example, IP_PACKET_BITCOUNT: typedef unsigned char ip_packet[(IP_PACKET_BITCOUNT / CHAR_BIT) + (IP_PACKET_BITCOUNT % CHAR_BIT > 0)];

Write a function, void set_bits(ip_packet p, size_t bitfield_offset, size_t bitfield_width, unsigned char *value) { ... } , which allows you to set the bit starting with p[bitfield_offset / CHAR_BIT] bit bitfield_offset % CHARBIT to the bits found in the value up to bitfield_width bits in length. This will be the hardest part of your task.

You can then define the identifiers for VER_OFFSET 0 and VER_WIDTH 4, HLEN_OFFSET 4 and HLEN_WIDTH 4, etc., so that modifying the array seems less painless.

0
source

Depends on the compiler or not, depends on whether you want to write a very fast program or if you want to work with different compilers. To write a fast, compact application for C, use stuct with the / bit fields. If you want a slow general-purpose program, use a long code.

0
source

I agree with what he said. Bit fields are compiler dependent.

If you want the bit to be in a specific order, pack the data into a pointer to an array of characters. Increase the buffer whose size is being packaged. Pack the next item.

 pack( char** buffer ) { if ( buffer & *buffer ) { //pack ver //assign first 4 bits to 4. *((UInt4*) *buffer ) = 4; *buffer += sizeof(UInt4); //assign next 4 bits to 5 *((UInt4*) *buffer ) = 5; *buffer += sizeof(UInt4); ... continue packing } } 
-one
source

All Articles