在 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
我建议结合使用 #pragma pack
和 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;
内部匿名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 -->|
这样做是为了在您拥有字段数组时正确对齐字段。
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 -->|
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
我定义了一个结构,用于跨两个不同接口发送的消息。其中之一需要 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
我建议结合使用 #pragma pack
和 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;
内部匿名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 -->|
这样做是为了在您拥有字段数组时正确对齐字段。
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 -->|
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