Home > Blockchain >  Pad a packed struct in C for 32-bit alignment
Pad a packed struct in C for 32-bit alignment

Time:11-18

I have a struct defined that is used for messages sent across two different interfaces. One of them requires 32-bit alignment, but I need to minimize the space they take. Essentially I'm trying to byte-pack the structs, i.e. #pragma pack(1) but ensure that the resulting struct is a multiple of 32-bits long. I'm using a gcc arm cross-compiler for a 32-bit M3 processor. What I think I want to do is something like this:

#pragma pack(1)
typedef struct my_type_t
{
    uint32_t someVal;
    uint8_t anotherVal;
    uint8_t reserved[<??>];
}
#pragma pack()

where <??> ensures that the size of my_type_t is divisible by 4 bytes, but without hard-coding the padding size. I can do something like this:

#pragma pack(1)
typedef struct wrapper_t
{
    my_type_t m;
    uint8_t reserved[sizeof(my_type_t)   4 - (sizeof(my_type_t) % 4)]
}
#pragma pack()

but I'd like to avoid that.

Ultimately what I need to do is copy this to a buffer that is 32-bit addressable, like:

static my_type_t t; //If it makes a difference, this will be declared statically in my C source file
...
memcpy(bufferPtr, (uint32_t*)&t, sizeof(t)) //or however I should do this

I've looked at the __attribute__((align(N))) attribute, which gives me the 32-bit aligned memory address for the struct, but it does not byte-pack it. I am confused about how (or if) this can be combined with pack(1).

My question is this:

What is the right way to declare these structs so that I can minimize their footprint in memory but that allows me to copy/set it in 4-byte increments with a unsigned 32-bit pointer? (There are a bunch of these types of arbitrary size and content). If my approach above of combining pack and padding is going about this totally wrong, I'll happily take alternatives.

Edit:

Some constraints: I do not have control over one of the interfaces. It is expecting byte-packed frames. The other side is 32-bit addressable memory mapped registers. I have 64k of memory for the entire executable, and I'm limited on the libraries etc. I can bring in. There is already a great deal of space optimization I've had to do.

The struct in this question was just to explain my question. I have numerous messages of varying content that this applies to.

CodePudding user response:

I can't speak for the specific compiler and architecture you are using, but I would expect the following to be sufficient:

typedef struct {
   uint32_t x;
   uint8_t  y;
} my_type_t;

The structure normally has the same alignment as its largest field, and that includes adding the necessary padding at the end.

my_type_t
 --------------- 
| x             |
 --- ----------- 
| y | [padding] |
 --- ----------- 

|<-- 32 bits -->|

Demo

This is done so the fields are properly aligned when you have an array of them.

my_type_t my_array[2];

my_array[1].x = 123;  // Needs to be properly aligned.

The above assumes you have control over the order of the fields to get the best space efficiency, because it relies on the compiler aligning the individual fields. But those assumptions can be removed using GCC attributes.

typedef struct {
   uint8_t  x;
   uint32_t y;
   uint8_t  z;
}
   __attribute__((packed))      // Remove interfield padding.
   __attribute__((aligned(4)))  // Set alignment and add tail padding.
   my_type_t;

This produces this:

my_type_t
 --- ----------- 
| x | y         
 --- --- ------- 
    | z | [pad] |
 --- --- ------- 

|<-- 32 bits -->|

Demo

The packed attribute prevents padding from being added between fields, but aligning the structure to a 32-bit boundary forces the alignment you desire. This has the side effect of adding trailing padding so you can safely have an array of these structures.

CodePudding user response:

As you use gcc you need to use one of the attributes.

Example demo.

#define PACKED __attribute__((packed))
#define ALIGN(n) __attribute__((aligned(n)))

typedef struct 
{
    uint8_t anotherVal;
    uint32_t someVal;
}PACKED my_type_t;


my_type_t t = {1, 5};
ALIGN(64) my_type_t t1 = {1, 5};
ALIGN(512) my_type_t t2 = {2, 6};

int main()
{
    printf("%p, %p, %p", (void *)&t, (void *)&t1, (void *)&t2);
}

Result:

0x404400, 0x404440, 0x404600

https://godbolt.org/z/j9YjqzEYW

CodePudding user response:

I suggest combining #pragma pack with alignas:

#include <stdalign.h>
#include <stdint.h>

typedef struct {
    #pragma pack(1)
    alignas(4) struct {      // requires 2 1 2 bytes but is aligned to even 4:s
        uint16_t someVal;    //  0
        uint8_t anotherVal;  //  2
        uint16_t foo;        //  3 (would be 4 without packing)
    };
    #pragma pack()
} my_type_t;

The anonymous inside struct makes access easy as before:

int main() {
    my_type_t y;
    y.someVal = 10;
    y.anotherVal = 'a';
    y.foo = 20;

    printf("%zu\n", (char*)&y.someVal - (char*)&y.someVal);    // 0
    printf("%zu\n", (char*)&y.anotherVal - (char*)&y.someVal); // 2
    printf("%zu\n", (char*)&y.foo - (char*)&y.someVal);        // 3

    my_type_t x[2];
    printf("%zu\n", (char*)&x[1] - (char*)&x[0]); // 8 bytes diff
}

If you'd like to be able to take the sizeof the actual data carrying part of my_type_t (to send it), you could name the inner struct (which makes accessing the fields a little more cumbersome):

#pragma pack(1)
typedef struct {
    uint16_t someVal;
    uint8_t anotherVal;
    uint16_t foo;
} inner;
#pragma pack()

typedef struct {
    alignas(4) inner i;
} my_type_t;

You'd now have to mention i to access the fields, but it has the benefit that you can take sizeof and get 5 (in this example):

int main() {
    my_type_t y;

    printf("%zu %zu\n", sizeof y, alignof(y));   // 8 4
    printf("%zu\n", sizeof y.i);                 // 5    (the actual data)
}

CodePudding user response:

To form a structure type that is aligned one must put the alignment attribute to the first member of the struct. It can be combined with the packed attribute.

typedef struct {
    _Alignas(4) uint8_t anotherVal;
    uint32_t someVal;
} __attribute__((packed)) my_type_t;

Exemplary usage with alignment exaggerated to 64 bytes.

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

typedef struct {
    _Alignas(64) uint8_t anotherVal;
    uint32_t someVal;
} __attribute__((packed)) my_type_t;

int main() {
    my_type_t a, b;
    printf("%zu %p\n", sizeof a, (void*)&a);
    printf("%zu %p\n", sizeof b, (void*)&b);
}

prints:

64 0x7ffff26caf80
64 0x7ffff26cafc0
  • Related