这种类型的修饰是否定义明确?

时间:2019-01-17 14:23:04

标签: c language-lawyer

我的结构如下。

struct result{
    int a;
    int b;
    int c;
    int d;
}

和下面的联合。

union convert{
   int arr[4];
   struct result res;
}

然后按如下所示输入pun。

  int arr1[4] = {1,2,3,5};
  union convert *pointer = (union convert *) arr1; // Here is my question, is it well defined?

  printf("%d %d\n", pointer->res.a, pointer->res.b);

4 个答案:

答案 0 :(得分:12)

pointer->res.a很好,但是pointer->res.b的行为未定义。

ab成员之间可以有任意数量的填充。

某些编译器允许您指定成员之间没有填充,但是您当然放弃了可移植性。

答案 1 :(得分:6)

  

这种类型的定义是否正确?

struct result{
    int a,b,c,d;
}

union convert {
   int arr[4];
   struct result res;
}

int arr1[4] = {1,2,3,5};
union convert *pointer = (union convert *) arr1; 

(union convert *) arr1可能会导致对齐失败。

  

指向对象类型的指针可以转换为指向不同对象类型的指针。如果生成的指针未针对引用的类型正确对齐,则行为未定义。 C11dr§6.3.2.38

不要求union convertint共享相同的对齐方式。例如,union convert个要求可能超过int

请考虑以下可能性:arr1[]居住在所有地址均为4的倍数的int街道上。unionstruct朋友居住在“ 8的倍数”街道上。 arr1[]的地址可能是0x1004(不是8的倍数)。

在2019年,对齐失败更常见于char(需要1)和需要2个或更多的其他类型。在OP的精选案例中,我怀疑真正的平台会出现对齐问题,但仍然可能会出现错误的对齐情况。

这种类型的双关定义不明确。


其他问题

其他答案和comments讨论了填充问题,进一步确定了问题所在。

@Eric Postpischil关于对pointer->res.a的不正确的访问的评论,增加了考虑此UB的更多原因。

答案 2 :(得分:2)

C对在结构的两个连续成员之间还剩下多少填充量没有规定。

这就是为什么实现定义了许多#pragma指令的原因-特别是为了更改此行为。

因此,正如Bathsheba的回答所说,...->b是不确定的。

前段时间,我回答了同样的问题,here

答案 3 :(得分:1)

指针修剪不安全。而是使用真正的联合修剪。

假设:结构已正确打包(成员之间没有填充)

#include <stdio.h>
#include <string.h>



struct __attribute__((packed)) result{
    int a;
    int b;
    int c;
    int d;
};

union convert{
   int arr[4];
   struct result res;
};

  volatile int arr1[4];

void foo(void)
{

  union convert cnv;

  memcpy(&cnv, (void *)arr1, sizeof(arr1));

  printf("%d %d\n", cnv.res.a, cnv.res.b);
}

所有现代编译器都会优化memcpy调用

https://godbolt.org/z/4qtRIF

.LC0:
        .string "%d %d\n"
foo:
        mov     rsi, QWORD PTR arr1[rip]
        xor     eax, eax
        mov     rdi, QWORD PTR arr1[rip+8]
        mov     edi, OFFSET FLAT:.LC0
        mov     rdx, rsi
        sar     rdx, 32
        jmp     printf