作为联合成员的类型可以为该联合起别名吗?

Can a type which is a union member alias that union?

提示:

C11 standard 声明指向联合的指针可以转换为指向其每个成员的指针。来自第 6.7.2.1p17 节:

The size of a union is sufficient to contain the largest of its members. The value of at most one of the members can be stored in a union object at any time. A pointer to a union object, suitably converted, points to each of its members (or if a member is a bit-field, then to the unit in which it resides), and vice versa.

这意味着您可以执行以下操作:

union u {
    int a;
    double b;
};

union u myunion;
int *i = (int *)&u;
double *d = (double *)&u;

u.a = 2;
printf("*i=%d\n", *i);
u.b = 3.5;
printf("*d=%f\n", *d);

但是反过来呢:在上述联合的情况下,int *double * 可以安全地转换为 union u * 吗?考虑以下代码:

#include <stdio.h>

union u {
    int a;
    double b;
};

void f(int isint, union u *p)
{
    if (isint) {
        printf("int value=%d\n", p->a);
    } else {
        printf("double value=%f\n", p->b);
    }
}

int main()
{
    int a = 3;
    double b = 8.25;
    f(1, (union u *)&a);
    f(0, (union u *)&b);
    return 0;
}

在这个例子中,指向 intdouble 的指针,它们都是 union u 的成员,被传递给一个需要 union u * 的函数。一个标志被传递给函数,告诉它要访问哪个"member"。

假设像本例一样,访问的成员与实际传入的对象的类型相匹配,那么上面的代码是否合法?

我在 gcc 6.3.0 上用 -O0-O3 编译了这个并且都给出了预期的输出:

int value=3
double value=8.250000

关于严格别名,从指向类型的指针(例如 &a)到包含该类型的指向联合的指针没有问题。它是严格别名规则的例外之一,C17 6.5/7:

An object shall have its stored value accessed only by an lvalue expression that has one of the following types:
- a type compatible with the effective type of the object, /--/
- an aggregate or union type that includes one of the aforementioned types among its members

所以只要 union 包含 int/double,就严格别名而言这很好。指针转换本身也是明确定义的。

当您尝试访问内容时出现问题,例如 int 的内容作为更大的 double。由于多种原因,这可能是 UB - 我至少可以想到 C17 6.3.2.3/7:

A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned69) for the referenced type, the behavior is undefined.

非规范脚注提供更多信息的地方:

69) In general, the concept “correctly aligned” is transitive: if a pointer to type A is correctly aligned for a pointer to type B, which in turn is correctly aligned for a pointer to type C, then a pointer to type A is correctly aligned for a pointer to type C.

没有。这在形式上是不正确的。

在 C 中你可以做任何事情,它可以工作,但像这样的构造是炸弹。任何未来的修改都可能导致大失败。

联合会保留内存 space 以容纳 最大 个元素:

The size of a union is sufficient to contain the largest of its members.

相反,space 还不够。

考虑:

union
{
    char a;
    int b;
    double c;
} myunion;
char c;
((union myunion *)&c)->b = 0;

会造成内存损坏。

标准定义的含义:

The value of at most one of the members can be stored in a union object at any time. A pointer to a union object, suitably converted, points to each of its members (or if a member is a bit-field, then to the unit in which it resides), and vice versa.

强制每个联合成员都从联合起始地址开始,并且隐含地声明编译器应在适合其每个元素的边界上对齐联合,这意味着为每个成员选择正确的对齐方式。因为标准对齐方式通常是 2 的幂,根据经验,并集将在适合需要最大对齐方式的元素的边界上对齐。

In this example, pointers to int and double, both of which are members of union u, are passed to a function where a union u * is expected. A flag is passed to the function to tell it which "member" to access.

Assuming, as in this case, that the member accessed matches the type of the object that was actually passed in, is the above code legal?

您似乎将分析重点放在联合成员类型的严格别名规则方面。然而,鉴于

union a_union {
    int member;
    // ...
} my_union, *my_union_pointer;

,我倾向于争辩说 my_union.membermy_union_pointer->member 形式的表达式除了访问 union a_union 类型的对象之外还表示访问成员的类型。因此,如果 my_union_pointer 实际上没有指向有效类型为 union a_union 的对象,那么确实违反了严格的别名规则——关于类型 union a_union——并且因此行为未定义。

标准没有给出使用成员类型的左值访问 structunion 对象的一般权限,也没有——据我所知——它是否给出任何特定的权限执行此类访问,除非该成员恰好是字符类型。它也没有定义任何将 int* 转换为 union u* 的行为可以创建一个不存在的方法。相反,创建任何将作为 union u 访问的存储意味着在该存储中同时创建 union u 对象。

相反,标准(引用自 C11 草案 N1570)依赖于应用脚注 88 的实现(此列表的目的是指定对象可以或不可以的情况别名。) 并认识到 "strict aliasing rule" (6.5p7) 只应在对象通过其自身类型的左值和 看似无关的 在函数或循环的某些特定执行期间另一种类型的左值[即当对象别名其他一些左值时]。

何时可以将两个左值视为 "seemingly unrelated",以及何时应该期望实现识别它们之间的关系,这是一个实现质量问题。 Clang 和 gcc 似乎认识到 unionPtr->valueunionPtr->value[index] 形式的左值与 *unionPtr 相关,但似乎无法认识到指向此类左值的指针与 unionPtr 有任何关系。因此,他们将认识到 unionPtr->array1[i]unionPtr->array2[j] 都访问 *unionPtr(因为通过 [] 进行的数组下标似乎与数组到指针衰减的处理方式不同),但不会认识到 *(unionPtr->array1+i)*(unionPtr->array2+j) 也是如此。

附录--标准参考:

给定

union foo {int x;} foo,bar;
void test(void)
{
  foo=bar;   // 1
  foo.x = 2; // 2
  bar=foo;   // 3
}

标准将 foo.x 的类型描述为 int。如果第二条语句没有访问 foo 的存储值,那么第三条语句将没有效果。因此,第二条语句使用 int 类型的左值访问 union foo 类型对象的存储值。看N1570 6.5p7:

An object shall have its stored value accessed only by an lvalue expression that has one of the following types:(footnote 88)

  • 与对象的有效类型兼容的类型,
  • 与对象的有效类型兼容的类型的合格版本,
  • 对象的有效类型对应的有符号或无符号类型,
  • 一个类型,它是对应于对象的有效类型的合格版本的有符号或无符号类型,
  • 在其成员中包含上述类型之一的聚合或联合类型(递归地包括子聚合或包含的联合的成员),或
  • 一种字符类型。

Footnote 88) The intent of this list is to specify those circumstances in which an object may or may not be aliased.

请注意,上面没有给出使用类型 int 的左值访问类型 union foo 的对象的权限。因为以上是一个约束,任何违反它的行为都会调用 UB ,即使构造的行为将由标准.

定义。