当前位置:  开发笔记 > 编程语言 > 正文

关于C - union中的union作为一种类型并且读作另一种类型的问题 - 是否已实现定义?

如何解决《关于C-union中的union作为一种类型并且读作另一种类型的问题-是否已实现定义?》经验,为你挑选了5个好方法。

我正在阅读K&R中关于C的联合,据我所知,联合中的单个变量可以包含几种类型中的任何一种,如果某些东西存储为一种类型并且提取为另一种,则结果纯粹是实现定义的.

现在请检查以下代码段:

#include

int main(void)
{
  union a
  {
     int i;
     char ch[2];
  };

  union a u;
  u.ch[0] = 3;
  u.ch[1] = 2;

  printf("%d %d %d\n", u.ch[0], u.ch[1], u.i);

  return 0;
}

输出:

3 2 515

在这里,我在分配值u.ch,但来自检索u.chu.i.它是实现定义的吗?或者我做的事情真的很傻?

我知道这对其他大多数人来说似乎都是初学者,但我无法弄清楚输出背后的原因.

谢谢.



1> Alex B..:

这是未定义的行为.u.iu.ch位于相同的内存地址.因此,写入一个并从另一个读取的结果取决于编译器,平台,体系结构,有时甚至是编译器的优化级别.因此输出u.i可能并不总是如此515.

例如,gcc在我的机器上为-O0和产生两个不同的答案-O2.

    因为我的机器具有32位小端架构,-O0我最终将两个最低有效字节初始化为2和3,两个最重要的字节未初始化.所以联盟的记忆如下:{3, 2, garbage, garbage}

    因此我得到的输出类似于3 2 -1216937469.

    有了-O2,我得到3 2 515你喜欢的输出,这使得联盟记忆{3, 2, 0, 0}.会发生什么是使用实际值gcc优化调用printf,因此程序集输出看起来像是等效于:

    #include 
    int main() {
        printf("%d %d %d\n", 3, 2, 515);
        return 0;
    }
    

    值515可以如在该问题的其他答案中解释的那样获得.本质上,它意味着当gcc优化调用时,它选择了零作为未初始化联合的随机值.

写入一个联盟成员并从另一个联盟成员阅读通常没有多大意义,但有时它可能对使用严格别名编译的程序有用.


这个答案是不正确的.在C 1999和C 2011中,读取除最后存储的成员之外的联合成员本身并不是未定义的.字节在新类型中重新解释.具体细节是实现定义的,而不是未定义的.这可能导致陷阱表示,导致未定义的行为,但这是新值的结果,而不是联合成员访问的结果,并且根据所涉及的特定类型,可以由标准完全定义.
根据[本缺陷报告](http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_283.htm),技术勘误3对C 1999进行了具体修改.

2> AnT..:

这个问题的答案取决于历史背景,因为语言的规范随着时间而变化.这件事恰好是受变化影响的人.

你说你正在读K&R.该书的最新版本(截至目前)描述了C语言的第一个标准化版本 - C89/90.在那个版本的C语言中,写一个联合成员并读取另一个成员是未定义的行为.没有实现定义(这是一个不同的东西),但未定义的行为.在这种情况下,语言标准的相关部分是6.5/7.

现在,在C的演化的后期(C99版本的语言规范与技术勘误3应用),突然变得合法使用联合进行类型惩罚,即写一个联盟成员然后读另一个.

请注意,尝试执行此操作仍可能导致未定义的行为.如果您读取的值对于您通读的类型无效(所谓的"陷阱表示"),则行为仍未定义.否则,您读取的值是实现定义的.

您的具体示例对于从数组intchar[2]数组的类型是相对安全的.在C语言中,将任何对象的内容重新解释为char数组总是合法的(同样,6.5/7).

然而,反之则不然.将数据写入联合的char[2]数组成员然后将其读取为int可能会创建陷阱表示并导致未定义的行为.即使您的char数组有足够的长度来覆盖整个数据库,也存在潜在的危险int.

但是在你的特定情况下,如果int碰巧大于char[2],int你读到的将覆盖数组末尾之外的未初始化区域,这又会导致未定义的行为.


你确定这是对的吗?您可以通过`memcpy`从另一个`int`创建一个有效的`int`,它将它组装为`unsigned char`单元(表示).我相信只要您有某种方法可以确保创建有效的表示形式,这样做同样有效.请注意,(非常常见)条件`INT_MIN == - (2 ^(CHAR_BIT*sizeof(int)-1))`确保所有表示都有效.
@ninjalj:你是对的.TC3注释82说"如果用于访问union对象的内容的成员与上次用于在对象中存储值的成员不同,则将值的对象表示的适当部分重新解释为对象表示在6.2.6中描述的新类型(有时称为"类型双关"的过程).这可能是陷阱表示."这个答案是错误的; 从存储的最后一个成员之外的联合成员中读取本身并不是未定义的行为.

3> Amnon..:

输出背后的原因是在您的机器上整数以小端格式存储:首先存储最不重要的字节.因此,字节序列[3,2,0,0]表示整数3 + 2*256 = 515.

此结果取决于具体实现和平台.


技术上未定义,而不是实现定义.这些术语在标准中有不同的含义.

4> Amarghosh..:

它取决于实现,结果可能因不同的平台/编译器而异,但似乎正是这样:

二进制515是

1000000011

填充零使其成为两个字节(假设16位为int):

0000001000000011

这两个字节是:

00000010 and 00000011

这是23

希望有人解释为什么他们被颠倒了 - 我的猜测是,字符不会被颠倒,但int是小端.

分配给union的内存量等于存储最大成员所需的内存量.在这种情况下,你有一个int和一个长度为2的char数组.假设int是16位而char是8位,两者都需要相同的空间,因此union被分配了两个字节.

将三个(00000011)和两个(00000010)分配给char数组时,union的状态为0000001100000010.当您从此联合中读取int时,它会将整个事物转换为整数.假设LSB存储在最低地址的little-endian表示,从union读取的int将是0000001000000011515的二进制.

注意:即使int是32位也是如此 - 检查Amnon的答案



5> Ferruccio..:

此类代码的输出将取决于您的平台和C编译器实现.您的输出让我觉得您在litte-endian系统(可能是x86)上运行此代码.如果您将515放入i并在调试器中查看它,您会看到最低位的字节为3,而内存中的下一个字节为2,它完全映射到您放入ch的内容.

如果你在big-endian系统上这样做,你可能(可能)得到770(假设16位整数)或50462720(假设32位整数).

推荐阅读
135369一生真爱_890
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有