Home > Software engineering >  How to pack structs in array and remove zero-padding?
How to pack structs in array and remove zero-padding?

Time:11-14

To my understanding, "there will never be padding in between elements of an array". And I understand that a struct has to be minimum one byte long, otherwise it will be padded with zeros.

I want to have an array of structs, each of size 4 bits, without the zero-padding. Is there some kind of "packing" I can apply to the array?

I want my output to be 0xFFFF (0b1111_1111_1111_1111), but I cannot get rid of the padding of the struct.

#include <stdio.h>
#include <stdint.h>
#include <string.h>

int main() {

    struct data_struct {
        unsigned a: 1;
        unsigned b: 3;
    } __attribute__((packed));  // avoid structure padding

    union {
        struct data_struct data[4];
        uint16_t data_uint;
    } union_data;

    memset(&union_data.data_uint, 0, sizeof(union_data.data_uint));
    for (int i = 0; i < 4;   i) {
        union_data.data[i].a = 1;
        union_data.data[i].b = 7;
    }

    printf("union_data = 0xX\n", union_data.data_uint);  // 0x0F0F  == 0b0000_1111_0000_1111
    return 0;
}

CodePudding user response:

Is there some kind of "packing" I can apply to the array?

No, there is not. Byte is the lowest addressable unit, and it's at least 8 bits - so all variables will be aligned to at least 8 bits and will have a size of at least 8 bits.

How to pack structs in array and remove zero-padding?

Don't. Write accessor functions and use bit operations to assign and retrieve the data. Prefer to write portable code.

Prefer not to use bitfields either - note that the order of bitfields inside a byte (LSB vs MSB) is implementation defined, and the padding between bitfields is also implementation defined. To be portable, write accessor functions with bit operations.

The idea is that second and 4th element in struct data_struct data[4] would start in the middle of byte boundary - that is not possible. For your case, you have to extract the data from the packed union inside properly aligned structure, if you want to access them that way:

  union union_data_t {
        struct {
            unsigned char a1 : 1;
            unsigned char b1 : 3;
            unsigned char a2 : 1;
            unsigned char b2 : 3;
        } data[2];
        uint16_t data_uint;
   } union_data;
   struct mydata union_data_get(union union_data_t *t, unsigned idx) {
       struct mydata r;
       r.a = idx%2 ? t->data[idx/2].a2 : t->data[idx/2].a1;
       r.b = idx%2 ? t->data[idx/2].b2 : t->data[idx/2].b1;
       return r;
   }
   void union_data_get(union union_data_t *t, unsigned idx, struct mydata mydata) {
       if (idx%2) { t->data[idx/2].a2 = mydata.a; }
       else { t->data[idx/2].a1 = mydata.a; }
       if (idx%2) { t->data[idx/2].b2 = mydata.b; }
       else { t->data[idx/2].b1 = mydata.b; }
   }

sounds like the best gcc-specific abstraction, but now there's no reason to use bitfields anyway - the accessor functions could just be written using bit operations anyway:

#include <stdio.h>
#include <stdint.h>
#include <string.h>

struct data_struct {
    unsigned a: 1;
    unsigned b: 3;
} __attribute__((packed));  // avoid structure padding

struct data_struct data_struct_array_get(unsigned char *t, unsigned idx) {
    const unsigned mask = 4 * (idx % 2);
    unsigned v = (t[idx/2] >> mask) & 0xf;
    return (struct data_struct){v>>3, v};
}
void data_struct_array_set(unsigned char *t, unsigned idx, struct data_struct data) {
    const unsigned v = data.a << 3 | data.b;
    const unsigned mask = 4 * (idx % 2);
    t[idx/2] &= ~(0xf << mask);
    t[idx/2] |= v << mask;
} 

int main() {
    union union_data_t {
        unsigned char data[2];
        uint16_t data_uint;
    } union_data;
    
    for (int i = 0; i < 4;   i) {
        data_struct_array_set(union_data.data, i, 
            (struct data_struct){1, 7}
        );
    }

    printf("union_data = 0xX\n", union_data.data_uint);
    return 0;
}
  • Related