初始化具有共同初始序列的两个结构的并集

问题描述 投票:3回答:2

问题:如果我们使用一个结构初始化初始序列的某些部分而使用另一个结构初始序列的其余部分初始化初始序列的某些部分,那么如果union包含两个具有共同初始序列兼容类型的结构吗?

请考虑以下代码片段:

union u_t{
    struct {
        int i1;
        int i2;
    } s1;

    struct {
        int j1;
        int j2;
    } s2;
};

int main(){
    union u_t *u_ptr = malloc(sizeof(*u_ptr));
    u_ptr -> s1.i1 = 10;
    u_ptr -> s2.j2 = 11;

    printf("%d\n", u_ptr -> s2.j1 + u_ptr -> s1.i2); //prints 21
}

DEMO

问题是“印刷21”行为是否明确。标准N1570 6.5.2.3(p6)指定以下内容:

如果一个union包含几个共享一个公共初始序列的结构(见下文),并且如果union对象当前包含这些结构中的一个,则允许在完成类型的声明的任何地方检查它们中的任何一个的公共初始部分。工会是可见的。

因此,检查公共初始序列(在这种情况下是整个结构)是可以的。但问题是,在这种情况下,联合似乎包含s2对象,j2是唯一的初始化成员。

我认为我们最终没有指定行为,因为我们只初始化s2.j2,而s2.j1没有,所以它应该包含未指定的值。

c language-lawyer unions
2个回答
2
投票

关于别名:

公共初始序列仅涉及两种结构类型的别名。这不是问题,你的两个结构甚至是兼容类型,因此指向它们的指针可能不使用任何技巧。解剖C11 6.2.7:

6.2.7兼容型和复合型 如果类型相同,则两种类型具有兼容类型。 / - /此外,如果它们的标记和成员满足以下要求,则在单独的翻译单元中声明的两个结构,联合或枚举类型是兼容的:

如果使用标记声明一个,则使用相同的标记声明另一个。

这里没有使用标记声明struct。

如果两者都在各自的翻译单元内的任何地方完成,则以下附加要求适用:

它们都已完成(已定义)。

其成员之间应存在一对一的对应关系,以使每对相应的成员被宣布为兼容类型;

这适用于这些结构。

如果使用对齐说明符声明该对中的一个成员,则使用等效的对齐说明符声明另一个成员;如果该对的一个成员使用名称声明,则另一个成员使用相同的名称声明。

对齐说明符不适用。

对于两个结构,相应的成员应按相同的顺序声明。

这是正确的。

结论是你的两个结构都是兼容的类型。这意味着您不需要像常见的初始序列那样的任何技巧。严格别名规则只是说明(6.5 / 7):

对象的存储值只能由具有以下类型之一的左值表达式访问: - 与对象的有效类型兼容的类型,

这就是这种情况。

此外,正如其他答案中所提到的,这里实际数据的有效类型是int,因为分配的存储不会产生有效类型,因此它成为用于左值访问的第一种类型。这也意味着编译器不能假设指针不会别名。

此外,严格别名规则为结构和联合的成员的左值访问提供了一个例外:

聚合或联合类型,包括其成员中的上述类型之一

然后你就有了共同的初始序列。就混叠而言,这是可以定义的。


关于打字:

你的实际关注似乎不是别名,而是通过工会打字。 C11 6.5.2.3/3模糊地保证了这一点:

后缀表达式后跟。运算符和标识符指定结构或联合对象的成员。该值是指定成员的值,95)如果第一个表达式是左值,则它是左值。

这是规范性文本而且写得很糟糕 - 没有人能够理解程序/编译器应该如何基于此行为。信息足迹95)解释得很好:

95)如果用于读取union对象内容的成员与上次用于在对象中存储值的成员不同,则该值的对象表示的适当部分将被重新解释为新对象表示按6.2.6中描述的类型(有时称为“类型双关”的过程)。这可能是陷阱表示。

在您的情况下,您可以触发从一种结构类型到另一种兼容结构类型的类型转换。这是完全安全的,因为它们是完全相同的类型,并且对齐或陷阱的问题不适用。

请注意,C ++与此不同。


1
投票

C11标准(n1570)在footnote[6.5 Expressions]/6中指出:

已分配的对象没有声明的类型。

并且[6.5 Expressions]/6说:

6访问其存储值的对象的有效类型是对象的声明类型(如果有)。如果通过具有非字符类型的左值的值将值存储到没有声明类型的对象中,则左值的类型将成为该访问的对象的有效类型以及不修改该值的后续访问的有效类型储值。

当您在[6.5 Expressions]/7语句中访问存储的打印值时,您也遵循printf中的规定。

这与你从N1570 6.5.2.3(p6)提供的引用相结合,提供“为了简化工会的使用而做出的一个特殊保证”使得这个定义明确。

在实践方面,如果你看看assembly generated,你会发现这就是实际发生的事情。

        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     eax, 8
        mov     edi, eax
        call    malloc
        mov     qword ptr [rbp - 8], rax //Here
        mov     rax, qword ptr [rbp - 8] //Here
        mov     dword ptr [rax], 10      //Here 
        mov     rax, qword ptr [rbp - 8] //Here
        mov     dword ptr [rax + 4], 11  //Here 
        mov     rax, qword ptr [rbp - 8]
        mov     ecx, dword ptr [rax]
        mov     rax, qword ptr [rbp - 8]
        add     ecx, dword ptr [rax + 4]
        movabs  rdi, offset .L.str
        mov     esi, ecx
        mov     al, 0
        call    printf
        xor     ecx, ecx
        mov     dword ptr [rbp - 12], eax # 4-byte Spill
        mov     eax, ecx
        add     rsp, 16
        pop     rbp
        ret
.L.str:
        .asciz  "%d\n"
© www.soinside.com 2019 - 2024. All rights reserved.