当前位置: 首页 > 知识库问答 >
问题:

为结构和数组指定别名是否合法?

陈宜修
2023-03-14

在结构中相同类型的连续成员之间的指针算术曾经是一种常见做法,而指针算术仅在数组中有效。在C中,它将是显式的未定义行为,因为数组只能由声明或新表达式创建。但是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;
}

这在这里实际上没有意义,但是我已经看到了真实世界的例子,其中在结构的成员之间迭代允许更简单的代码,并且输入错误的风险更小。

问题:

在上面的示例中,static_assert是否足以使结构的别名与数组合法?

(注1)由于结构描述的是按顺序分配的非空成员对象集,因此后续成员必须具有递增的地址。简单地说,编译器可以包括它们之间的填充。因此,如果 3 倍大小 (int) 加上它之前的总填充,则最后一个成员(此处为 t)的偏移量。如果偏移量正好是 3 * 大小(int),则结构中没有填充

作为副本提出的问题包含一个被接受的答案和一个1答案,前者让let认为它是UB,后者让let认为它是合法的,因为我可以确保不存在填充

共有3个答案

岳意蕴
2023-03-14

我要和UB争论。首先,6.5.6添加剂操作员的强制性引用:

当具有整数类型的表达式被添加到指针或从指针中减去时,结果具有指针操作数的类型。如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向从原始元素偏移的元素,使得结果数组元素和原始数组元素的下标之差等于整数表达式。换句话说,如果表达式P指向数组对象的第I个元素,则表达式(P) N(等价地,N (P))和(P)-N(其中N的值为N)分别指向数组对象的第In个和第i-n个元素,前提是它们存在。此外,如果表达式P指向数组对象的最后一个元素,则表达式(P) 1指向数组对象的最后一个元素之后的一个元素,如果表达式Q指向数组对象的最后一个元素之后的一个元素,则表达式(Q)-1指向数组对象的最后一个元素。如果指针操作数和结果都指向同一个数组对象的元素,或者都超过数组对象的最后一个元素,则求值不应产生溢出;否则,行为是未定义的。如果结果比数组对象的最后一个元素多一位,则它不能用作一元*运算符的操作数。

我强调了我认为问题的关键。你说数组对象是“具有特定成员对象类型(称为元素类型)的连续分配的非空对象集”,这是对的。但是反过来是真的吗?连续分配的一组对象构成数组对象吗?

我要说不。对象需要显式创建。

因此,对于您的示例,没有数组对象。在 C 中通常有两种方法可以创建对象,使用自动、静态或线程本地持续时间声明它们。或者分配它们并为存储提供有效的类型。您没有创建数组。这使得算术正式未定义。

宗政子辰
2023-03-14

这里的问题是您对连续分配的定义:“我们可以确保成员是连续的(意味着它们之间没有填充)”。

尽管这是连续分配的必然结果,但它并没有定义属性。

您的结构成员是具有自动存储持续时间的单独变量,按照特定顺序进行填充或不填充,这取决于您如何控制编译器,仅此而已。因此,给定另一个成员的地址,您不能使用指针算术来到达一个成员,并且这样做的行为是未定义的。

谷梁玺
2023-03-14

不,将struct和数组别名为这样是不合法的,这违反了严格的别名。解决方法是将结构包装在联合中,该联合包含数组和单个成员:

union something {
  struct quad {
    int x;
    int y;
    int z;
    int t;
  };

  int array [4];
};

这将避免严格的别名冲突,但您可能仍然具有填充字节。您可以使用静态断言检测到它。

另一个问题仍然存在,那就是你不能在指向结构的第一个成员的< code>int*上使用指针算法,因为在加法运算符的指定行为中列出了各种模糊的原因——它们要求指针指向一个数组类型。

避免所有这些的最佳方法是简单地使用上面联合的数组成员。这与静态断言一起产生定义明确,坚固耐用且可移植的代码

(理论上,您也可以使用指向字符类型的指针来遍历结构——不像int*,这是允许的6.3.2.3/7.但如果您对单个字节不感兴趣,这是一个更混乱的解决方案。)

 类似资料:
  • 怎么判断一个数组是否是以下数据结构,并且判断这些字段名都得存在,可以多但是以下字段必须存在 希望得到结果

  • 我有以下损坏的docker compose文件 此操作失败,并显示以下错误 问题1:如何在中合并数组?我尝试使用的语法是用于合并字典的语法 问题2:如果没有办法合并数组,有没有变通方法? 用例:我有多个服务,其中一些映射一些卷,另一些映射其他卷,另一些映射所有卷。我不想重复我自己。 谢谢你!

  • 我有一个支持以下操作的数据结构: 可以在固定时间内插入项目。对于该项,数据结构分配一个唯一的正整数。(说明:指定的整数不是插入项的函数,用户对指定的整数没有选择权。它完全由数据结构选择。) 它是使用指针数组实现的,其中指定的整数是存储项的索引。未使用的索引以链表方式链接起来,以便进行固定时间的插入。 这种数据结构的名称是/应该是什么?

  • 本文向大家介绍数组和结构之间的区别,包括了数组和结构之间的区别的使用技巧和注意事项,需要的朋友参考一下 在本文中,我们将了解数组和结构之间的区别。 数组 它指的是由相同/相同数据类型的元素组成的集合。 它使用下标/'[]'(方括号)来访问元素。 它是指向集合的第一个元素的指针。 数组对象无法实例化。 数组的大小基于数组中元素的数量是固定的。 此大小是元素数量与每个元素的大小的乘积。 数组中不能使用

  • 我还尝试使用了optional(),但唯一的区别是regex包含了一个‘?’在最后。JSON断言仍然失败。 在存根中,两个结果都返回,但对于测试,我希望测试也能成功。测试断言纯粹是在每个属性的最后一次出现时生成的吗?难道没有可能在数组上使用类似'optional()'的东西吗?

  • 问题内容: 我想有一个通用的方法,无论它是作为指针,切片还是数组提供的,都将始终返回结构值。 我对此的处理方式如下: 去游乐场 如您所见,问题出在从a 或。中获取结构。 我如何扩展上面的函数以从数组或切片中获取struct的值? 更新:我要做的就是将变成。 问题答案: 如果你只是想要的类型,即使片是零,你可以使用像这样: 关于,来自http://golang.org/pkg/reflect/#Ty