4

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.

Alex
  • 827
  • 8
  • 18
  • 1
    I'd add a static assertion on the size of the struct, and maintain the padding manually (not that I am aware of a nice way to do it automatically). – Eugene Sh. Nov 17 '21 at 21:52
  • 2
    Just don't use `pragma pack` at all. – EOF Nov 17 '21 at 21:57
  • 1
    @EOF It is often the least expensive way to build a communication frame to be serialized. – Eugene Sh. Nov 17 '21 at 21:59
  • @EOF my intent is to minimize the padding as much as possible. I don't want padding among every field in the struct, I want the minimum 0-3 bytes at the end such that the total size is 4-byte divisible – Alex Nov 17 '21 at 22:00
  • @Alex If your example is not representative of your actual problem, please construct a [mre]. – EOF Nov 17 '21 at 22:04
  • Regarding *"What is the right way"* - packing the `struct`s are usually deprecated, so it is hardly the "right" way, as @EOF said. It's just that there is no better alternative which won't add considerable overhead. – Eugene Sh. Nov 17 '21 at 22:08
  • I have many structures defined with varying sets and types of fields. I was hoping for a generic, automatic way to do this for any struct. (i.e. a compiler flag or macro, etc). The example in this question is representative of the problem. I don't think it's necessary to add more examples, the fundamental issues is the struct is not divisible by 4 bytes. I have two constraints as stated in the question: the struct needs to be 4-byte divisible and minimized in memory (the purpose of byte packing) – Alex Nov 17 '21 at 22:08
  • Use a proper serialization library. – EOF Nov 17 '21 at 22:09
  • @EOF Serialize memory mapped registers or result received from the IC via I2C (you may ask vendor to make a special version for you - it usually does not cost more than $1M). Good luck. Maybe you have not noticed but the question is about M3 microcontroller. – 0___________ Nov 17 '21 at 22:10
  • 1
    @0___________ Your point being? – EOF Nov 17 '21 at 22:12
  • 1
    I don't know anything about "32-bit M3 processor", but won't `struct { uint32_t someVal; uint8_t anotherVal; }` create exactly what you want if you don't use `#pragma` packed or anything else either? The structure normally has the same alignment as its largest field, and that includes adding the necessary padding at the end – ikegami Nov 17 '21 at 22:14
  • @EOF *`Use a proper serialization library`* It is your idea. Isn't it? – 0___________ Nov 17 '21 at 22:15
  • I updated the question with more details – Alex Nov 17 '21 at 22:17
  • @igegami - imagine that you receive dataa from the IC using DMA and format is 8 bits, 32 bits, 16 bits. – 0___________ Nov 17 '21 at 22:17
  • 1
    @0___________ I have to admit to not understanding why you're even talking to me. However, if you're actually accessing memory-mapped registers via a `packed` struct, you're already pretty hosed, given that the compiler may decide to access the bytes individually then, which the hardware may not like at all. – EOF Nov 17 '21 at 22:19
  • @ikegami `uint8_t, uint32_t, uint8_t` will be of different size than `uint8_t, uint8_t, uint32_t` if not packed. – Eugene Sh. Nov 17 '21 at 22:21
  • [This question](https://stackoverflow.com/questions/66066859/is-it-possible-to-pad-a-c-c-structure-to-a-fixed-size-using-a-compile-time-cal) is about padding a structure to a fixed size, and some of the ideas in it may be useful for padding to a desired multiple rather than a fixed size. For example, the base structure could be a packed anonymous structure inside a structure or union with the desired alignment requirement. – Eric Postpischil Nov 18 '21 at 01:53

4 Answers4

2

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.

ikegami
  • 367,544
  • 15
  • 269
  • 518
  • If my understanding is correct, this will result in padding around every field that isn't a 32-bit integer, which leads to a much larger struct than one that is byte packed. I.e. if I have multiple `uint8_t ` fields they will introduce 3 bytes of padding each. – Alex Nov 17 '21 at 22:31
  • https://arm.godbolt.org/z/hsE84581r - as you can see the order in which the fields are coming affect the size of the struct – Eugene Sh. Nov 17 '21 at 22:31
  • @Alex, It depends on the order. I'll add a note to that effect. Do you have control over the order the fields? – ikegami Nov 17 '21 at 22:32
  • Unfortunately I do not, they are specified by the device I'm communicating with (which includes the byte packed frame issue). The memory mapped buffers on the other side I have much more control over. I'm trying to avoid two different declarations of the same types though. – Alex Nov 17 '21 at 22:34
  • @Alex, ok, then are any of the fields misaligned? i.e. do you have a 32-bit word that's not an offset divisible by 4? If not, if all the fields are aligned, then this could work for you. – ikegami Nov 17 '21 at 22:35
1

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

0___________
  • 60,014
  • 4
  • 34
  • 74
  • 1
    I'd imagine the OP requirement is to have `sizeof(my_type_t) % 4 == 0`. Maybe it is not needed in practice though – Eugene Sh. Nov 17 '21 at 22:11
  • To make sure I understand this correctly, would it then be "safe" to access these using a 32-bit pointer like the copy I included in the question? i.e. can I safely write into these with a `uint32_t * strctPtr = (uint32_t*)&t`, 32-bits at a time? – Alex Nov 17 '21 at 22:37
  • @Alex, If alignment is the only issue, and if `t` had `ALIGN(4)`, yes. – ikegami Nov 17 '21 at 22:40
  • By attaching the alignment to the var rather than the type, `sizeof(my_type_t)` won't include the padding, and arrays of `my_type_t` won't have aligned elements. [Demo](https://godbolt.org/z/o84E7h469) – ikegami Nov 17 '21 at 23:03
1

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)
}
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
1

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
tstanisl
  • 13,520
  • 2
  • 25
  • 40