0

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 = 0x%04X\n", union_data.data_uint);  // 0x0F0F  == 0b0000_1111_0000_1111
    return 0;
}
HennyKo
  • 712
  • 1
  • 8
  • 19
  • A previous comment proposed to use memcpy. But the problem is that my struct is lesser than one byte and to my understanding memcpy only copied bytes. – HennyKo Nov 13 '21 at 09:22

1 Answers1

3

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 = 0x%04X\n", union_data.data_uint);
    return 0;
}
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • Thank you very much. The last code snippet is perfect as it allows for easy adjustment of the array size. – HennyKo Nov 13 '21 at 09:51
  • In your first code snippet, the last function should be `set` not `get`. I need to change 6 chars, otherwise stackoverflow does not allow me to edit your post. – HennyKo Nov 13 '21 at 09:53
  • Maybe I talked too soon. I am unable to set a struct of `a:2` and `b:3` for example. even with `const unsigned mask = 5 * (idx % 2); t[idx / 2] &= ~(0x1F << mask);` I think this only works for writing 4bits because of the `idx/2`. – HennyKo Nov 13 '21 at 10:32
  • 1
    That sounds like a good another question - post the problem description (packing 5-bit elements into an array of unsigned char), post your code and how your code fails. Surely, 8 is not a multiple of 5 - so there will be elements that are crossing the byte boundary, so there will be need to write to two bytes at a time to write one element. And, you can also approuch the problem more abstractly, instead of creating an array of n-bit elements, create an abstract [bit-stream](https://gitlab.com/Kamcuk/kamillibc/-/blob/master/libs/libc/src/bitstr/bitstr.c#L70), and stream 5 times 1 bit at a time. – KamilCuk Nov 13 '21 at 11:40