我的结构如下。
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);
答案 0 :(得分:12)
pointer->res.a
很好,但是pointer->res.b
的行为未定义。
a
和b
成员之间可以有任意数量的填充。
某些编译器允许您指定成员之间没有填充,但是您当然放弃了可移植性。
答案 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 convert
和int
共享相同的对齐方式。例如,union convert
个要求可能超过int
。
请考虑以下可能性:arr1[]
居住在所有地址均为4的倍数的int
街道上。union
和struct
朋友居住在“ 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
调用
.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