【问题标题】:Packing a union of structs打包结构的联合
【发布时间】:2016-12-22 09:36:32
【问题描述】:

我想创建一个由不同大小的不同结构组成的数组。

生成的数组必须紧密打包,结构之间没有空值。

整个东西必须在编译时初始化,所以它可以驻留在嵌入式系统的闪存中。

结果是一棵 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 打包属性并没有改变标准联合数组的行为。

【问题讨论】:

  • 这个问题是不应该使用结构/联合进行序列化的原因之一。
  • 工会不是这样工作的。
  • 感谢有关拥有单个大型结构的想法。不幸的是,完整的结构取决于配置,一个配置可能有 1 个端点,另一个可能有三个,其中每个端点是另一个结构。 GCC 不支持使用可变长度数组嵌套结构,并且将它们设置为固定大小会破坏可重用性。
  • 数组的所有元素必须是相同的大小。这是因为用于访问第 N 个元素的代码将一个元素的 N x 大小添加到数组的基地址中。如果元素的大小不同,那根本行不通。这是您不能拥有具有灵活数组成员的结构类型数组的原因之一。
  • union 修饰符意味着结构占用相同的内存,因此在一个结构中设置一个字段会影响另一个结构中的一个或多个字段。更好的数据表示是每个结构都是一个单独的实例,然后有一个指针数组,其中每个指针指向一个结构实例

标签: c arrays linker usb unions


【解决方案1】:

我想创建一个由不同大小的不同结构组成的数组。

这在 C 中根本不可能(并且有充分的理由)。数组(在 C 中)由 相同 大小(和类型)的组件组成。如果不是这种情况,则对该数组元素的索引访问将是一项非常复杂且耗时的操作(这与 C 的精神背道而驰;但是在 C++ 中,您可以定义自己的 operator [])。

您可以改为有一个 char-s 数组(例如 const char data[] = {0x35, 0x27, 0}; 等;也许这个大字节数组可以由一些临时脚本发出一些 C 代码初始化一个大数组)并有一些 @ 987654321@ 例程来处理它。或者你可以有一个 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},
};

请注意,在您的情况下,拥有一个指针数组实际上会提供更大的数据。

【讨论】:

    【解决方案2】:

    您不应该为此使用union,它不会像您认为的那样做。如果您想要一个结构数组,其中每个结构可能具有不同的类型,则无法完成。相反,您必须以正确的顺序定义一个包含所有其他结构的“超级结构”。

    但是,这并不能解决对齐/填充问题。为了禁用结构(和联合)中的填充,您必须使用非标准 C。常见的非标准扩展是 #pragma pack。 gcc 编译器还支持非标准属性“packed”,参见What is the meaning of “attribute((packed, aligned(4))) ”。由于禁用填充的代码是非标准的,因此它也是不可移植的。

    也可以通过创建一个uint8_t 数组来解决这个问题,然后将数据块读/写到这个数组中。这称为数据的序列化/反序列化。从任何指针类型到uint8_t* 或字符类型的转换都是安全的,但不幸的是,反过来会调用未定义的行为。这是因为 C 语言中的一个错误(通常称为 "the strict aliasing rule")导致在进行此类与硬件相关的编程时,有时无法以流畅或有意义的方式使用 C 语言。

    解决这个 C 语言错误的方法是编写一个包含 2 个元素的巨大联合,一个是 uint8_t 数组,一个是像上面描述的那样的“超级结构”。您实际上不会使用超级结构-您可能因为填充而不能使用-但是通过将其放在联合中,您会调用严格别名的特殊异常。这意味着将不再有未定义的行为,并且您将防止像 gcc 这样的积极优化编译器破坏您的代码。

    另一个针对这个 C 语言错误的 gcc 特定解决方法是使用 gcc -fno-strict-aliasing 进行编译。在这种情况下,嵌入式系统编译器通常比 gcc 工作得更好,因为它们不遵循 C 标准,而是使指针转换以非标准方式确定性地运行。例如,在此类编译器上,(uint16_t*)my_uint8t 之类的代码确定性地将指向的数据视为uint16_t,而不是默默地导致程序崩溃和烧毁。

    【讨论】:

      【解决方案3】:

      接受来自@Basile-Starynkevitch、@Jonathan-Leffler 和其他我希望无法实现的 cmets,我重新考虑了。我真正需要的是精确控制结构在内存/闪存中的相对位置。放置是通过链接器完成的,我最终在那里找到了解决方案。

      首先,在链接描述文件的 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) 属性确保它们被紧紧地包装在一起,没有空白空间。

      这解决了我的问题,配置定义是通过结构完成的,因为它提供了所有优点,丑陋被 #define 隐藏,最终配置是一个可变长度的二进制 blob,由 uint8_t* 指针访问.随着指针的增加,它会在不同的配置元素之间无缝移动。

      【讨论】:

        猜你喜欢
        • 2015-07-31
        • 1970-01-01
        • 1970-01-01
        • 2019-06-20
        • 1970-01-01
        • 2010-11-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多