Ser*_*sta 5 c arrays struct language-lawyer
结构中相同类型的连续成员之间的指针算术过去常常是一种常见的做法,而指针算术仅在数组内有效.在C++中,它将是明确的Undefined Behavior,因为数组只能由声明或新表达式创建.但是C语言将数组定义为具有特定成员对象类型的连续分配的非空对象集,称为元素类型.(n1570 C11草案,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;
}
Run Code Online (Sandbox Code Playgroud)
它在这里没有意义,但我已经看到了真实世界的例子,在结构的成员之间进行迭代允许更简单的代码,而错误的风险更低.
题:
在上面的例子中,是否static_assert足以使用数组合法化结构的别名?
(注释1)由于结构描述了顺序分配的非空成员对象集,后面的成员必须具有增加的地址.只需编译器可以在它们之间包含填充.所以最后一个成员(这里t)的偏移量是3 sizeof(int)倍加上之前的总填充量.如果偏移正是3 * sizeof(int)那么结构中没有填充
建议作为副本的问题包含一个可接受的答案,让我们认为它将是UB,以及+1答案,让我们认为它可能是合法的,因为我可以确保不存在填充
我要和UB争论一下。首先,也是最重要的,来自6.5.6 加法运算符的强制引用:
当具有整数类型的表达式与指针相加或相减时,结果具有指针操作数的类型。如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向距原始元素的元素偏移量,使得结果数组元素和原始数组元素的下标之差等于整数表达式。换句话说,如果表达式 P 指向 数组对象的第 i 个元素,则表达式 (P)+N (相当于 N+(P))和 (P)-N (其中 N 的值为 n)指向分别为数组 object的第 i+n 个和第 in 个元素(前提是它们存在)。此外,如果表达式 P 指向数组对象的最后一个元素,则表达式 (P)+1 指向数组对象的最后一个元素后一位,如果表达式 Q 指向数组对象的最后一个元素后一位,表达式 (Q)-1 指向数组对象的最后一个元素。如果指针操作数和结果都指向同一个数组对象的元素,或者超过数组对象的最后一个元素,则求值不会产生溢出;否则,行为是未定义的。如果结果指向数组对象的最后一个元素,则不应将其用作所计算的一元 * 运算符的操作数。
我强调了我认为问题的关键所在。当您说数组对象是“具有特定成员对象类型(称为元素类型)的连续分配的非空对象集”时,您是对的。但反之亦然吗?连续分配的一组对象是否构成数组对象?
我要说不。需要显式创建对象。
因此,对于您的示例,没有数组对象。在 C 中创建对象通常有两种方法。使用自动、静态或线程本地持续时间来声明它们。或者分配它们并为存储指定有效类型。您没有创建数组。这使得算术正式未定义。