C结构体:连续的字段没有填充?

4

any_t表示任意类型(如intstruct something等)。

考虑以下结构:

struct my_struct {
    any_t val,
    any_t array[10]
}

如果我定义一个变量v
struct my_struct v;

使用&v.val作为11个any_t项的数组是否安全?

any_t *p = &v.val;
f(p[0]);
f(p[5]);
f(p[10]);

保证在valarray之间不会添加任何填充吗?


3
您可以使用 #pragma pack(1) 来确保成员之间没有空格。 - Kevin
这篇讨论“为什么结构体的sizeof不等于每个成员变量sizeof之和”的stackoverflow帖子值得一读。 - yeyo
3个回答

7
从C标准来看,使用&v.val作为11个any_t数组是不安全的,原因如下:
  • C标准允许在结构体中存在未命名的内部填充:C 2011 (N1570) 6.7.2.1 15,“结构体对象内可能有未命名的填充,但不能在其开头处有填充”。虽然一个C实现通常不会在valarray之间插入填充,因为对齐要求不需要,但这是允许的,并且在某些情况下可能是有益的,例如使array更好地对齐以提高性能(而不是必需)。
  • 即使存在确保valarray元素之间间距与11个any_t数组相同的保证,也无法保证指针算术运算有效。 C 2011 (N1570) 6.5.6 8定义了指针算术运算(包括数组索引),它只要求算术运算在一个数组中有效(包括一个虚拟元素在末尾)。一些C实现使用基地址和偏移地址寻址。在这种情况下,val的基地址可能无法支持延伸到array的偏移量。
  • 即使C实现使用简单的平面寻址,其优化器也允许根据C标准进行推断。优化器可以理论上看到指针是从val地址派生的,因此不能(按照C标准)用于寻址array中的任何内容。例如,如果您执行any_t *p = &v.val; p[i] = 3; v.array[j] = 4;,优化器可能将对v.array[j]p[i]的赋值视为独立的,并以任何顺序执行它们,即使您设置了ij以使它们指向同一元素。

标准似乎在指针超出数组声明大小时,对于实现可以或不可以合法地做什么有些模糊;至今我还不知道任何事情能够证明或废除“传统”的“结构体技巧”(使用大小为一的数组)。然而,关于您后面提到的点,我认为如果一个结构体在malloc分配的空间内,将元素的地址转换为void*,然后再转回其元素类型,应该保证如果事物确实位于正确的位置,那么事情应该能够正常工作。 - supercat
除非特别指定了具体的实现方式,否则我认为没有任何一种通用的方法可以使用类似数组访问的方式来访问结构体成员,除非定义了一个偏移量数组来明确列出结构体成员(另外,不能保证成员之间的间隔是相等的)。但是我认为,如果结构体位于malloc分配的空间中,那么它的成员肯定会与其中适当的字节重叠。 - supercat
使用有关地址空间的模型来推断C语义是不正确的(例如,假设成员将位于某些字节偏移量上,因此它们形成一个数组),因为C标准没有定义这样的模型。要正确推理,必须从标准中的语句推断行为。正如我所指出的,优化器可能会做出意外的事情。即使您使用malloc分配空间,将v设置为指向它,并且所有元素都位于预期的偏移量上,如果您将any_t *p = &v->val设置为i,则优化器可以表现得好像p[i]不是v->array[j] - Eric Postpischil

1
不,标准没有任何保证。尽管如此,你的具体编译器实现可能会提供超出标准保证的保证。查阅文档以获取所有详细信息。

对齐要求不相关。valarray 成员将具有相同的对齐要求,因此由于对齐要求而导致的 val 填充与对齐要求引起的 array 元素填充相同。标准允许在 valarray 之间存在未命名的填充,但实现不会插入用于对齐要求的填充。(假设它可能会插入用于对齐的填充,例如,如果它有助于将 array 对齐到缓存行,并且编译器有理由认为这将是有益的。) - Eric Postpischil
好的,第二句话已经删除。 - Carl Norum

0

你的结构体定义只有两个字段,所以编译器可能不会将其用作11个元素的数组,因此你需要按照它们声明的方式填充你的字段。


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