工会成员的类型可以别名吗?

时间:2019-02-04 14:52:02

标签: c language-lawyer unions strict-aliasing

this question提示:

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

  

联合的大小足以容纳最大的   它的成员。最多一个成员的值可以是   随时存储在并集对象中。 指向工会的指针   经过适当转换的对象指向其每个成员(或   如果成员是位域,则指向它所在的单元   居住,反之亦然。

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

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;
}

在此示例中,指向int成员的doubleunion u的指针被传递到期望union u *的函数。将一个标志传递给该函数以告诉它要访问哪个“成员”。

假设在这种情况下,所访问的成员与实际传递的对象的类型匹配,那么上面的代码合法吗?

我在-O0-O3的gcc 6.3.0上进行了编译,并都给出了预期的输出:

int value=3
double value=8.250000

4 个答案:

答案 0 :(得分:4)

  

在此示例中,指向int和double的指针,它们都是成员   联合u的值传递给期望联合u *的函数。一种   标志传递给该函数,以告知该函数访问哪个“成员”。

     

在这种情况下,假设所访问的成员与类型匹配   实际传递的对象中,上述代码合法吗?

您似乎将分析重点放在对工会成员类型的严格别名规则上。但是,给定

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而言-因此,行为是不确定的。

答案 1 :(得分:2)

该标准不授予使用成员类型的左值访问structunion对象的一般权限,就我所知,该标准也不授予执行权限的任何特定权限除非成员碰巧是字符类型,否则这种访问。它也没有定义将int*转换为union u*的行为可以创建尚不存在的任何方法。相反,创建任何将以union u身份访问的存储都意味着在该存储中同时创建一个union u对象。

相反,该标准(引用自C11草案N1570的引用)依赖于实施以应用脚注88(此列表的目的是指定对象可能会别名或可能不会别名的那些情况。< / em>),并认识到“严格别名规则”(6.5p7)仅应在通过自身类型的左值和另一类型的看似无关左值同时被引用时应用在某个特定的函数或循环执行期间[即当对象为其他左值别名时。

何时将两个左值视为“看似无关”以及何时应期望实现识别它们之间的关系是一个实现质量问题。 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的存储值,那么第三条语句将无效。因此,第二条语句使用类型union foo的左值访问类型int的对象的存储值。看着N1570 6.5p7:

  

只能通过具有以下类型之一的左值表达式访问对象的存储值:(脚注88)

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

脚注88)此列表的目的是指定对象可能会别名也可能不会别名的那些情况。

请注意,上面没有授予使用类型union foo的左值访问类型int的对象的权限。因为上述是一个约束,所以任何违反行为都会调用UB ,即使该构造的行为否则将由标准定义。

答案 2 :(得分:1)

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

  

只能通过具有以下类型之一的左值表达式访问对象的存储值:
  -与对象的有效类型兼容的类型/-/
  -集合或联合类型,其中包括上述类型之一   成员

因此,只要union包含int / double,就严格的别名而言这是可以的。指针转换本身也是定义良好的。

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

  

指向对象类型的指针可以转换为指向不同对象类型的指针。如果对于所引用的类型,结果指针未正确对齐 69),则行为未定义。

非规范性脚注提供了更多信息:

  

69)通常,“正确对齐”的概念是可传递的:如果类型A的指针与类型B的指针正确对齐,   依次将其正确对齐以输入C类型的指针,然后将类型A的指针正确对齐以用于类型C的指针。

答案 3 :(得分:-1)

不。形式上不正确。

在C语言中,您可以做任何事情,并且它可以工作,但是像这样的构造就是炸弹。任何未来的修改都可能导致重大故障。

联合会保留存储空间来容纳其中的最大元素:

  

工会的规模足以容纳工会中的最大工会   成员。

反之,空间不足。

考虑:

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

将导致内存损坏。

标准定义的含义:

  

最多可以将一个成员的值存储在一个联合中   随时反对。指向并集对象(经过适当转换)的指针,   指向其每个成员(或者如果一个成员是位域,则指向   它所在的单元),反之亦然。

强制每个联盟成员从联盟起始地址开始,并暗含声明编译器应针对其每个元素在合适的边界上对齐联盟,这意味着选择一个每个成员的对齐方式正确。因为标准路线通常是2的幂,根据经验法则,并会在与需要最大路线的元素相匹配的边界上进行路线对齐。