【问题标题】:Strange output when using union type members使用联合类型成员时的奇怪输出
【发布时间】:2020-10-22 19:32:16
【问题描述】:

我的代码:

#include<stdio.h>

union U{
    int x;
    char y;
};

int main()
{
    union U u1;
    u1.x = 258;
    u1.y = '0';
    printf("%d%d",u1.x,u1.y);
    return 0;
}

奇怪的是,输出是30448

有人能解释一下这是怎么发生的吗?

【问题讨论】:

  • 你期待什么?
  • 它只能读取已分配的联合的最后一个成员。
  • 还要注意'0'0 不是一回事,这可能是您混淆的一个因素。
  • 这是未定义的行为,但发生了某事258 是十六进制的102,所以在写入后四个字节包含02 01 00 00(小端序)。然后用'0' 覆盖第一个字节,即十六进制30。所以这四个字节现在是30 01 00 00,而十六进制130 是十进制304。但是因为没有输出空格,所以下一个值 hex 30 decimal 48 跑进去了,输出304 48 显示为30448
  • 这能回答你的问题吗? Purpose of Unions in C and C++

标签: c types unions


【解决方案1】:

您可能误解了union 的用途。它意味着一次只存储一个变量,但这个变量可以有多种类型。最后存储的变量将覆盖前一个。

在您的情况下u1.y(即'0',需要提醒的是'0' 的1 字节ASCII 十进制表示是48),是最后存储的值,这对应于最后2 位数字当您以 ASCII 十进制表示形式打印 '0' 时,您的输出。

至于输出的第一部分,请注意您覆盖了 int 变量 258,它大概是 4 个字节(但为了解释起见,我假设它是 2 个字节),其中 1 个字节宽 @ 987654330@变量48

258 的二进制值(假设 2 字节宽 int)是:

|0|0|0|0|0|0|0|1|0|0|0|0|0|0|1|0|

|   2nd byte    |   1st byte    |              

48(1 字节宽 char 变量)的二进制值是:

| | | | | | | | |0|0|1|1|0|0|0|0|

                |   1st byte    | 

当你用一个字节变量覆盖两个字节联合变量时,只有 8 个最低有效位(最低有效字节)会被覆盖,所以你最终会得到:

|0|0|0|0|0|0|0|1|x|x|x|x|x|x|x|x|
| | | | | | | | |0|0|1|1|0|0|0|0|

|0|0|0|0|0|0|0|1|0|0|1|1|0|0|0|0|

这是304的二进制表示。

所以你的代码首先打印 2 字节宽(为了示例)int304,然后是 1 字节宽 int48'0' 的 ASCII int 表示) ,因此输出30448

请注意,此行为不是未定义。

ISO/IEC 9899:2017 N2176

§ 6.5.2.3

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

§ 6.2.6.2

6 - 当一个值存储在结构或联合类型的对象中时,包括在成员对象中,对应于任何填充字节的对象表示的字节采用未指定的值.51)结构或联合对象的值绝不是陷阱表示,即使结构或联合对象的成员的值可能是陷阱表示。

7 - 当一个值存储在联合类型对象的成员中时,对象表示的字节不对应于该成员但确实对应于其他成员采用未指定的值。

您可以使用以下方式进行确认:

printf("%p %p\n", (void*)&u1.x, (void*)&u1.y);

这将打印u1.xu1.y 的内存地址,你不会惊讶地发现它们是相同的。

【讨论】:

  • 对于 2 或更多 字节 little-endian int。对于消费者台式机和笔记本电脑以及类似设备以外的其他计算机不了解的人来说,4 字节 LE 是迄今为止最有可能的。
  • @dave_thompson_085 你的评论很有趣,也是真的。在任何情况下,我都没有试图准确地描绘字节顺序,只是对导致输出的事件序列进行可理解的可视化,而不管字节顺序如何,我相信这可能会进一步混淆 OP。
【解决方案2】:

你不能读取除了最后一个写入的工会成员

#include<stdio.h>
union U{
int x;
char y;
};

int main()
{
    union U u1;
    u1.x = 258; // write into member x: OK
    u1.y = '0'; // write into member y: OK
    printf("%d%d",u1.x,u1.y); // read both member x and y: WRONG
                              // can only read member y
    return 0;
}

【讨论】:

  • C 标准中没有规定禁止读取除最后修改的联合成员以外的联合成员,C 2018 6.5.2.3 表示读取成员会产生命名成员的值,注释 99 解释对象表示的适当部分被重新解释为新类型中的对象表示。
  • C11 J.1 -- 可移植性问题 -- 除了最后存储到 [未指定] 的联合成员之外,对应于联合成员的字节值。还有一个 few footnotes throughout 标准使问题有点模糊@EricPostpischil
  • 是的,它不可移植。但你可以做到。并指定了结果行为:联合的适当字节被重新解释为新类型。 “除了最后一个写入之外,你不能读取一个联合的成员”是错误的。
  • @EricPostpischil+ 在技术上写一个文本流(标准输出是),最后一个或唯一的(伪)行不被\n 终止是 impl-def 并且可以说可能是未定义的:-}
猜你喜欢
  • 2016-02-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-01
  • 1970-01-01
  • 2012-04-11
相关资源
最近更新 更多