结构中相同类型的连续成员之间的指针运算曾经是一种常见的做法,而指针运算仅在数组内部有效。在 C++ 中,这将是明确的未定义行为,因为数组只能通过声明或新表达式创建。但是 C 语言将数组定义为一组连续分配的非空对象,其中 特定的成员对象类型,称为元素类型。(C11 的 n1570 草案,6.2.5 类型 §20)。因此,只要我们可以确保成员是连续的(意味着它们之间没有填充),那么将其视为数组是合法的。
这是一个简化的示例,它在没有警告的情况下进行编译,并在运行时给出预期的结果:
#include <stdio.h>
#include <stddef.h>
#include <assert.h>
struct quad {
int x;
int y;
int z;
int t;
};
int main() {
// ensure members are consecutive (note 1)
static_assert(offsetof(struct quad, t) == 3 * sizeof(int),
"unexpected padding in quad struct");
struct quad q;
int *ix = &q.x;
for(int i=0; i<4; i++) {
ix[i] = i;
}
printf("Quad: %d %d %d %d\n", q.x, q.y, q.z, q.t);
return 0;
}
它在这里并没有真正的意义,但我已经看到了现实世界的例子,在这个例子中,在结构的成员之间迭代允许更简单的代码,并且拼写错误的风险更小。
问题:
在上面的例子中,是static_assert
足以使结构与数组的别名合法化吗?
(注 1)由于结构描述了一个顺序分配的非空成员对象集,后面的成员必须有递增的地址。编译器可以简单地在它们之间包含填充。所以最后一个成员的偏移量(这里是 t
)如果是 sizeof(int)
的 3 倍加上它之前的总填充。如果偏移量正好是 3 * sizeof(int)
然后结构中没有填充
作为重复项提出的问题包含一个可以认为是 UB 的已接受答案,以及一个 +1 answer。让我们认为这可能是合法的,因为我可以确保不存在填充p>
最佳答案
不,像这样给 struct
和数组起别名是不合法的,它违反了严格的别名。解决方法是将结构包装在一个 union 中,该 union 包含一个数组和各个成员:
union something {
struct quad {
int x;
int y;
int z;
int t;
};
int array [4];
};
这避免了严格的别名违规,但您可能仍然有填充字节。您可以使用静态断言检测到。
另一个问题仍然存在,那就是您不能在指向结构的第一个成员的 int*
上使用指针算法,因为加法的指定行为中概述的各种模糊原因运算符 - 它们要求指针指向数组类型。
避免所有这些的最好方法是简单地使用上面 union 的数组成员。这与静态断言一起产生定义明确、坚固且可移植的代码。
(理论上,您还可以使用指向字符类型的指针来遍历结构 - 与 int*
不同,这将根据 6.3.2.3/7 被允许。但这是一个更困惑的如果您对单个字节不感兴趣,请解决此问题。)
关于c - 别名结构和数组是否合法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48147422/