嵌入式C++中零大小数组的替代方法

3
多重引导规范有如下结构:
struct multiboot_tag_mmap
{
  multiboot_uint32_t type;
  multiboot_uint32_t size;
  multiboot_uint32_t entry_size;
  multiboot_uint32_t entry_version;
  struct multiboot_mmap_entry entries[0];  
};

意图似乎是数组大小可变。这些信息在传递时由引导加载程序确定。在托管的C++中,建议使用“vector”。但是我不能这么做。另一个选择是使用动态分配,但这将需要在获取内存映射信息之前实现大量内核(分页,MMU等)功能。有点像先有鸡还是先有蛋的问题。
“hack”的方法是只需使用gnu++11启用扩展功能。但我尽可能避免使用扩展功能,以避免产生C式代码或潜在导致未定义行为的代码。我的观点是,代码越具有可移植性,出现bug的机会就越小。
最后,您可以像这样迭代内存映射:
for (mmap = ((struct multiboot_tag_mmap *) tag)->entries;
    (multiboot_uint8_t *) mmap
    < (multiboot_uint8_t *) tag + tag->size;
    mmap = (multiboot_memory_map_t *)
    ((unsigned long) mmap
    + ((struct multiboot_tag_mmap *) tag)->entry_size))

因此,结构的大小是tag->size

只要语义相同,我可以修改multiboot头文件。关键是它在引导加载程序中的显示方式。我能做什么?


1
可以将multiboot_tag_mmap分成两个结构体吗?感觉“头部”和“主体”应该是单独的对象,但要依次放置在同一内存块中。 - user2486888
只要你不声明 multiboot_tag_mmap 变量或者使用 sizeof(multiboot_tag_mmap),那么定义 entry 数组为 0、1 或 1000 个条目都无所谓。 (我假设你将一个预先存在于内存中的缓冲区转换为这种类型,然后迭代遍历条目) - Russ Schultz
@RussSchultz 零大小的数组根本不是合法的C++。此外,问题在于我不能浪费内存。我应该只使用我需要的那么多。 - user5514334
1
在 ISO C 中,您可以编写 struct multiboot_mmap_entry entries[];。也许您的编译器将其作为 C++ 的扩展功能? - M.M
1
在C语言中,不允许使用零大小的数组,因此我相当确定在C++中也是不允许的。在C语言中,您可以使用灵活数组成员。不确定C++是否已经支持了这个特性。 - Lundin
显示剩余2条评论
1个回答

2

不要使用大小为0的数组,可以使用大小为1的数组:

struct multiboot_tag_mmap
{
    ...
    struct multiboot_mmap_entry entries[1]; 
};

这将仅更改sizeof(struct multiboot_tag_mmap)的结果,但在任何情况下都不应使用它:分配的结构体大小应计算为

offsetof(struct multiboot_tag_mmap, entries) + <num-of-entries> * sizeof(struct multiboot_mmap_entry)

地图结构的对齐方式不取决于条目数组中元素的数量,而是取决于条目类型。

严格确认替代方案: 如果已知数组大小的边界,则可以使用该边界进行类型声明:

struct multiboot_tag_mmap
{
    ...
    struct multiboot_mmap_entry entries[<UPPER-BOUNDARY>]; 
};

对于这种声明,下面描述的所有可能问题都不适用。

关于元素访问的说明: 要访问(第一个之上的)这种灵活数组中的元素,需要声明新的指针变量:

struct multiboot_mmap_entry* entries = tag->entries;
entries[index] = ...; // This is OK.

不要直接使用entries字段,可以采用以下方式:

tag->entries[index] = ...; // WRONG! May spuriously fail!

事实上,编译器知道 entries 字段数组中只有一个元素存在,因此可以将最后一种情况进行优化:
tag->entries[0] = ...; // Compiler is in its rights to assume index to have the only allowed value

关于标准一致性的问题:采用灵活数组方法,内存中(在堆栈或堆上)不存在类型为struct multiboot_tag_mmap 的对象。所有我们拥有的是此类型的指针,该指针从未被引用过(例如,用于创建对象的完整副本)。同样地,与结构体中entries字段相对应的数组类型struct multiboot_mmap_entry[1] 也不存在,该字段仅用于转换为通用指针类型struct multiboot_mmap_entry*

因此,在C标准中表示未定义行为的短语

将一个对象分配给一个不精确重叠的对象或具有不兼容类型的具有精确重叠的对象

对于使用通用指针访问entries数组字段来说并不适用:这里不存在重叠对象


注意:访问entries[0]之外的内容会导致未定义的行为。 - M.M
为什么会导致UB?这类似于使用entries = malloc(<num-entries> * sizeof(entry))分配数组并将其用到entries[<num-entries> - 1]的技巧。 - Tsyvarev
@Lundin,我不确定关于别名问题,但是 sizeof(<type>) 考虑到填充,需要在一个数组中使用 <type>。因此,<num-of-entries> * sizeof(entry) 字节提供足够的内存,以便被解释为数组。 - Tsyvarev
顺便提一下,C89标准的第3.5.2.1段明确指出:“结构体或联合体末尾可能还有未命名的填充字节,以便在该结构体或联合体成为数组成员时实现适当的对齐。” - Tsyvarev
哦,我想我明白了你(和其他人)的意思:通过 tag->entries[index] 访问这种数组的元素实际上是不规范的。我添加了明确的使用说明,并提供了另一种绝对没有符合问题的替代方法。 - Tsyvarev
显示剩余9条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接