c - 别名结构和数组是否合法?

标签 c arrays struct language-lawyer

结构中相同类型的连续成员之间的指针运算曾经是一种常见的做法,而指针运算仅在数组内部有效。在 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。让我们认为这可能是合法的,因为我可以确保不存在填充

最佳答案

不,像这样给 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/

相关文章:

vector - 向量中的可变结构

c - 标记字符串后无法打印结果

c - 尝试从 C 数组中删除元素时出现意外结果

java.util.AbstractList.remove 处的 java.lang.UnsupportedOperationException(未知来源)

php - "Notice: Undefined variable"、 "Notice: Undefined index"、 "Warning: Undefined array key"和 "Notice: Undefined offset"使用 PHP

c - Security.h 中结构的 macOS 文档

c - uid_t 类型是有符号的还是无符号的?

c - C中的运行时限制计时器

c - 如何在while循环中处理多个相似条件?

c# - 类型转换明确布局的结构