在 C 中填充打包结构以实现 32 位对齐

Pad a packed struct in C for 32-bit alignment

我定义了一个结构,用于跨两个不同接口发送的消息。其中之一需要 32 位对齐,但我需要将它们占用的 space 最小化。本质上,我正在尝试对结构进行字节打包,即 #pragma pack(1) 但确保生成的结构是 32 位长的倍数。我正在为 32 位 M3 处理器使用 gcc arm 交叉编译器。我想我想做的是这样的:

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

其中 <??> 确保 my_type_t 的大小可被 4 个字节整除,但无需对填充大小进行硬编码。我可以这样做:

#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()

但我想避免这种情况。

最终我需要做的是将其复制到一个 32 位可寻址的缓冲区,例如:

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

我查看了 __attribute__((align(N))) 属性,它为我提供了结构的 32 位对齐内存地址,但它没有对其进行字节打包。我对如何(或是否)将其与 pack(1).

结合感到困惑

我的问题是:

声明这些结构的正确方法是什么,以便我可以最大限度地减少它们在内存中的占用空间,但又允许我使用无符号 32 位指针以 4 字节为增量 copy/set 它? (有一堆这些类型的任意大小和内容)。如果我上面结合 pack 和填充的方法是完全错误的,我会很乐意采取替代方案。

编辑:

一些限制:我无法控制其中一个界面。它期待字节打包的帧。另一侧是 32 位可寻址内存映射寄存器。我有 64k 的内存用于整个可执行文件,我在库等方面受到限制。我可以引入。我已经做了很多 space 优化。

这个问题中的结构只是为了解释我的问题。我有很多不同内容的消息适用于此。

当您使用 gcc 时,您需要使用其中一个属性。

示例 + 演示。

#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);
}

结果:

0x404400, 0x404440, 0x404600

https://godbolt.org/z/j9YjqzEYW

我建议结合使用 #pragma packalignas:

#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;

内部匿名struct让访问一如既往的简单:

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
}

如果您希望能够使用 sizeof my_type_t 的实际数据携带部分(发送),您可以将内部命名为 struct(使访问字段更加麻烦):

#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;

您现在必须提及 i 才能访问这些字段,但它的好处是您可以使用 sizeof 并获得 5(在此示例中):

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)
}

我无法说明您使用的具体编译器和体系结构,但我希望以下内容就足够了:

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

该结构通常与其最大字段具有相同的对齐方式,包括在末尾添加必要的填充。

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

|<-- 32 bits -->|

Demo

这样做是为了在您拥有字段数组时正确对齐字段。

my_type_t my_array[2];

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

以上假定您可以控制字段的顺序以获得最佳 space 效率,因为它依赖于编译器对齐各个字段。但是可以使用 GCC 属性删除这些假设。

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;

这会产生这个:

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

|<-- 32 bits -->|

Demo

packed 属性可防止在字段之间添加填充,但将结构与 32 位边界对齐会强制执行所需的对齐。这具有添加尾随填充的副作用,因此您可以安全地拥有这些结构的数组。

要形成对齐的结构类型,必须将对齐属性置于结构的第一个成员。它可以与 packed 属性结合使用。

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

对齐扩大到 64 字节的示例用法。

#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);
}

打印:

64 0x7ffff26caf80
64 0x7ffff26cafc0