包装结构的联合

时间:2016-12-22 09:36:33

标签: c arrays linker usb unions

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

结果数组必须紧密打包,结构体之间没有空值。

整个过程必须在编译时初始化,因此它可以驻留在嵌入式系统的flash中。

结果是一个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属性不会改变标准的联合数组行为。

3 个答案:

答案 0 :(得分:2)

  

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

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

你可以改为拥有一个char - s数组(例如const char data[] = {0x35, 0x27, 0};等;也许可以通过一些特殊的脚本生成一大块字节,从而发出一些初始化大数组的C代码)并有一些parsing例程来处理它。或者你可以有一个指针数组

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},
};

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

答案 1 :(得分:0)

你不应该使用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,而不是静默地导致程序崩溃和刻录。

答案 2 :(得分:0)

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

首先,在链接描述文件的SECTIONS部分中,我创建了一个特殊的块。确保顺序的唯一方法是创建多个部分并手动对它们进行排序,在此实例中为cpack0-3。

.text : ALIGN(4) /* Align the start of the block */
{
    *(.cpack0) *(.cpack1) *(.cpack2) *(.cpack3)
} > MFlash32

然后将struct变量插入特殊部分。在实际实现中,#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隐藏,最终配置是uint8_t*访问的可变长度的二进制blob。 1}}指针。随着指针递增,它会在不同的配置元素上无缝移动。