打包结构的联合
Packing a union of structs
我想创建一个具有不同大小的不同结构的数组。
生成的数组必须紧密打包,结构之间没有空值。
整个东西必须在编译时初始化,所以它可以驻留在嵌入式系统的闪存中。
结果是一棵 USB 配置描述符树,每个描述符都紧跟在最后一个之后,以生成单个配置 blob。欢迎提出解决该问题的不同方法的建议。 http://www.beyondlogic.org/usbnutshell/usb5.shtml#ConfigurationDescriptors
struct a {
uint16_t some_field;
};
struct b {
uint32_t another_field;
};
union detail {
struct a a;
struct b b;
};
const union detail configuration[] = {
{ .a = { .some_field = 23 } },
{ .b = { .another_field = 12 } }
};
以上示例是我当前失败尝试的显着简化版本。数组的每个元素都是最大联合成员的大小。所以每个数组成员都是 32 位,第一个条目用零填充。
当前输出1700 0000 0c00 0000
期望的输出1700 0c00 0000
生成此打包输出的现有方法使用带有宏的巨型 uint8 数组来插入更复杂的值,例如 16 位数。
结构数组可以更准确地表示数据并提供类型安全性(如果可行的话)。
我不需要能够索引或访问数组中的数据,blob 被推入低级 USB 例程。使用 gcc packed 属性并没有改变标准的联合数组行为。
I would like to create an array of different structs with different sizes.
这在 C 中根本不可能(并且有充分的理由)。数组(在 C 中)由 相同 大小(和类型)的组件组成。如果不是这种情况,对该数组元素的索引访问将是一个非常复杂且耗时的操作(这违背了 C 的精神;但是在 C++ 中,您可以定义自己的 operator []
)。
您可以改为使用 char
-s 的数组(例如 const char data[] = {0x35, 0x27, 0};
等;也许这个大字节数组可以由一些临时脚本生成,这些脚本发出一些 C 代码来初始化一个大的array) 并有一些 parsing 例程来处理它。或者你可以有一个数组 pointers:
union detail {
struct a* aptr;
struct b* bptr;
};
static const struct a firstelem= {.some_field= 35};
static const struct b secondelem= {.another_field= 12};
const union detail configuration[] = {
{.aptr= &firstelem},
{.bptr= &secondelem},
};
请注意,在您的情况下,指针数组实际上提供了更大的数据。
你不应该为此使用 union
,它并不像你认为的那样。如果您想要一个结构数组,其中每个结构可能属于不同类型,则无法完成。相反,您必须以正确的顺序定义一个包含所有其他结构的 "super struct"。
但是,这并不能解决 alignment/padding 的问题。为了禁用结构(和联合)中的填充,您必须求助于非标准 C。常见的非标准扩展是 #pragma pack
。 gcc 编译器还支持非标准属性 "packed",请参阅 What is the meaning of “attribute((packed, aligned(4))) ”。由于禁用填充的代码是非标准的,因此它也是不可移植的。
也可以通过创建 uint8_t
数组然后将 read/write 数据块放入该数组来解决问题。这被称为 serialization/de-serialization 的数据。从任何指针类型到 uint8_t*
或字符类型的转换都是安全的,但不幸的是,反过来会调用未定义的行为。这是因为 C 语言中的一个错误,通常被称为 "the strict aliasing rule",这有时会导致在进行诸如此类的与硬件相关的编程时,无法以流畅或有意义的方式使用 C 语言。
解决此 C 语言错误的方法是编写一个包含 2 个元素的巨大联合,一个是 uint8_t
数组,一个是 "super struct",如上文所述。您实际上不会使用 super 结构 - 您可能不能因为填充 - 但通过将它放在联合中,您可以调用严格别名的特殊例外。这意味着将不再有未定义的行为,并且您将防止像 gcc 这样的积极优化编译器破坏您的代码。
针对此 C 语言错误的另一个特定于 gcc 的解决方法是使用 gcc -fno-strict-aliasing
进行编译。在这种情况下,嵌入式系统编译器通常比 gcc 工作得更好,因为它们不遵循 C 标准,而是使指针转换以非标准方式确定性地运行。例如,在这样的编译器上,像 (uint16_t*)my_uint8t
这样的代码确定性地将指向的数据视为 uint16_t
,而不是默默地导致程序崩溃和烧毁。
接受来自@Basile-Starynkevitch、@Jonathan-Leffler 和其他人的评论,我重新考虑了我希望无法完成的事情。我真正需要的是精确控制 memory/flash 中结构的相对位置。放置是通过链接器完成的,我最终在那里找到了解决方案。
首先,我在链接描述文件的 SECTIONS 部分创建了一个特殊块。确保顺序的唯一方法是创建多个部分并手动对它们进行排序,在本例中为 cpack0-3。
.text : ALIGN(4) /* Align the start of the block */
{
*(.cpack0) *(.cpack1) *(.cpack2) *(.cpack3)
} > MFlash32
然后将结构变量放入特殊部分。在实际实现中,可以通过 #define 元素简化重复的长手语法。
const struct a configuration __attribute__((section(".cpack0"), aligned(1))) = {
.some_field = 23
};
const struct b configuration1 __attribute__((section(".cpack1"), aligned(1))) = {
.another_field = 12
};
所以我们有一个配置变量,在 4 字节地址处对齐以方便访问,并使用结构定义以确保类型安全。为了安全起见,配置的后续部分也由结构定义,并按顺序放置在内存中。 aligned(1)
属性确保它们紧密包装,没有空 space.
这解决了我的问题,配置定义是通过结构完成的,提供了所有优点,丑陋被 #define
隐藏,最终配置是可变长度的二进制 blob,由 a 访问uint8_t*
指针。随着指针递增,它会在不同的配置元素之间无缝移动。
我想创建一个具有不同大小的不同结构的数组。
生成的数组必须紧密打包,结构之间没有空值。
整个东西必须在编译时初始化,所以它可以驻留在嵌入式系统的闪存中。
结果是一棵 USB 配置描述符树,每个描述符都紧跟在最后一个之后,以生成单个配置 blob。欢迎提出解决该问题的不同方法的建议。 http://www.beyondlogic.org/usbnutshell/usb5.shtml#ConfigurationDescriptors
struct a {
uint16_t some_field;
};
struct b {
uint32_t another_field;
};
union detail {
struct a a;
struct b b;
};
const union detail configuration[] = {
{ .a = { .some_field = 23 } },
{ .b = { .another_field = 12 } }
};
以上示例是我当前失败尝试的显着简化版本。数组的每个元素都是最大联合成员的大小。所以每个数组成员都是 32 位,第一个条目用零填充。
当前输出1700 0000 0c00 0000
期望的输出1700 0c00 0000
生成此打包输出的现有方法使用带有宏的巨型 uint8 数组来插入更复杂的值,例如 16 位数。 结构数组可以更准确地表示数据并提供类型安全性(如果可行的话)。
我不需要能够索引或访问数组中的数据,blob 被推入低级 USB 例程。使用 gcc packed 属性并没有改变标准的联合数组行为。
I would like to create an array of different structs with different sizes.
这在 C 中根本不可能(并且有充分的理由)。数组(在 C 中)由 相同 大小(和类型)的组件组成。如果不是这种情况,对该数组元素的索引访问将是一个非常复杂且耗时的操作(这违背了 C 的精神;但是在 C++ 中,您可以定义自己的 operator []
)。
您可以改为使用 char
-s 的数组(例如 const char data[] = {0x35, 0x27, 0};
等;也许这个大字节数组可以由一些临时脚本生成,这些脚本发出一些 C 代码来初始化一个大的array) 并有一些 parsing 例程来处理它。或者你可以有一个数组 pointers:
union detail {
struct a* aptr;
struct b* bptr;
};
static const struct a firstelem= {.some_field= 35};
static const struct b secondelem= {.another_field= 12};
const union detail configuration[] = {
{.aptr= &firstelem},
{.bptr= &secondelem},
};
请注意,在您的情况下,指针数组实际上提供了更大的数据。
你不应该为此使用 union
,它并不像你认为的那样。如果您想要一个结构数组,其中每个结构可能属于不同类型,则无法完成。相反,您必须以正确的顺序定义一个包含所有其他结构的 "super struct"。
但是,这并不能解决 alignment/padding 的问题。为了禁用结构(和联合)中的填充,您必须求助于非标准 C。常见的非标准扩展是 #pragma pack
。 gcc 编译器还支持非标准属性 "packed",请参阅 What is the meaning of “attribute((packed, aligned(4))) ”。由于禁用填充的代码是非标准的,因此它也是不可移植的。
也可以通过创建 uint8_t
数组然后将 read/write 数据块放入该数组来解决问题。这被称为 serialization/de-serialization 的数据。从任何指针类型到 uint8_t*
或字符类型的转换都是安全的,但不幸的是,反过来会调用未定义的行为。这是因为 C 语言中的一个错误,通常被称为 "the strict aliasing rule",这有时会导致在进行诸如此类的与硬件相关的编程时,无法以流畅或有意义的方式使用 C 语言。
解决此 C 语言错误的方法是编写一个包含 2 个元素的巨大联合,一个是 uint8_t
数组,一个是 "super struct",如上文所述。您实际上不会使用 super 结构 - 您可能不能因为填充 - 但通过将它放在联合中,您可以调用严格别名的特殊例外。这意味着将不再有未定义的行为,并且您将防止像 gcc 这样的积极优化编译器破坏您的代码。
针对此 C 语言错误的另一个特定于 gcc 的解决方法是使用 gcc -fno-strict-aliasing
进行编译。在这种情况下,嵌入式系统编译器通常比 gcc 工作得更好,因为它们不遵循 C 标准,而是使指针转换以非标准方式确定性地运行。例如,在这样的编译器上,像 (uint16_t*)my_uint8t
这样的代码确定性地将指向的数据视为 uint16_t
,而不是默默地导致程序崩溃和烧毁。
接受来自@Basile-Starynkevitch、@Jonathan-Leffler 和其他人的评论,我重新考虑了我希望无法完成的事情。我真正需要的是精确控制 memory/flash 中结构的相对位置。放置是通过链接器完成的,我最终在那里找到了解决方案。
首先,我在链接描述文件的 SECTIONS 部分创建了一个特殊块。确保顺序的唯一方法是创建多个部分并手动对它们进行排序,在本例中为 cpack0-3。
.text : ALIGN(4) /* Align the start of the block */
{
*(.cpack0) *(.cpack1) *(.cpack2) *(.cpack3)
} > MFlash32
然后将结构变量放入特殊部分。在实际实现中,可以通过 #define 元素简化重复的长手语法。
const struct a configuration __attribute__((section(".cpack0"), aligned(1))) = {
.some_field = 23
};
const struct b configuration1 __attribute__((section(".cpack1"), aligned(1))) = {
.another_field = 12
};
所以我们有一个配置变量,在 4 字节地址处对齐以方便访问,并使用结构定义以确保类型安全。为了安全起见,配置的后续部分也由结构定义,并按顺序放置在内存中。 aligned(1)
属性确保它们紧密包装,没有空 space.
这解决了我的问题,配置定义是通过结构完成的,提供了所有优点,丑陋被 #define
隐藏,最终配置是可变长度的二进制 blob,由 a 访问uint8_t*
指针。随着指针递增,它会在不同的配置元素之间无缝移动。