代码之家  ›  专栏  ›  技术社区  ›  whacko__Cracko

关于C中的union的一个问题—存储为一种类型,读取为另一种类型—是否定义了实现?

  •  27
  • whacko__Cracko  · 技术社区  · 16 年前

    现在请检查此代码段:

    #include<stdio.h>
    
    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、 ch 和 u.i . 它是实现定义的吗?还是我在做一些非常愚蠢的事情?

    我知道对大多数人来说这似乎是个新手,但我无法找出输出背后的原因。

    6 回复  |  直到 8 年前
        1
  •  27
  •   Community Mohan Dere    9 年前

    u.i 和 u.ch u、 我 515 .

    例如 gcc 在我的机器上,为每个问题生成两个不同的答案 -O0 -O2 .

    1. 因为我的机器有32位的little endian架构 -O0 最后,我将两个最低有效字节初始化为2和3,两个最高有效字节未初始化。工会的记忆是这样的: {3, 2, garbage, garbage}

      3 2 -1216937469 .

    2. 具有 -氧气 ,我得到 3 2 515 {3, 2, 0, 0} . 发生了什么事 海湾合作委员会 优化对的调用 printf 具有实际值,因此程序集输出类似于:

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

      如对该问题的其他回答中所述,可获得值515。本质上,它意味着 海湾合作委员会 优化调用它选择了零作为可能未初始化的并集的随机值。

    给一个工会成员写信,读另一个工会成员的信 通常 it may be useful for programs compiled with strict aliasing

        2
  •  19
  •   AnT stands with Russia    13 年前

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

    未定义的行为 . 不 (这是另一回事),但是 未定义

    现在,在C语言发展的后期(应用了技术勘误3的C99语言规范版本),使用union进行类型双关(typepunning)突然变得合法,即写union的一个成员,然后读另一个成员。

    请注意,尝试这样做仍然会导致未定义的行为。如果您读取的值恰好对您读取的类型无效(所谓的“陷阱表示”),则该行为仍然未定义。否则,您读取的值是实现定义的。

    具体的 示例对于从 int 到 char[2] 数组。在C语言中,将任何对象的内容重新解释为字符数组(同样是6.5/7)是合法的。

    字符[2] 数组成员,然后将其作为 未定义的行为 . 即使您的字符数组长度足以覆盖整个字符,也存在潜在的危险 .

    但在你的特殊情况下,如果 int 字符[2] int

        3
  •  9
  •   Amnon    16 年前

    输出背后的原因是,在您的机器上,整数存储在 little-endian 格式:首先存储最低有效字节。因此是字节序列 [3,2,0,0]表示整数3+2*256=515。

    这一结果取决于具体的实现和平台。

        4
  •  5
  •   Ferruccio    16 年前

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

    如果在big-endian系统上执行此操作,则可能会得到770(假设为16位整数)或50462720(假设为32位整数)。

        5
  •  5
  •   Community Mohan Dere    9 年前

    这取决于实现,结果可能在不同的平台/编译器上有所不同,但似乎情况就是这样:

    515二进制是

    1000000011
    

    将零填充为两个字节(假设为16位整数):

    0000001000000011
    

    00000010 and 00000011
    

    那是 2 和 3

    希望有人能解释为什么它们是颠倒的-我的猜测是,字符不是颠倒的,但int是小端的。

    分配给联合体的内存量等于存储最大成员所需的内存量。在本例中,您有一个长度为2的int和char数组。假设int为16位,char为8位,两者都需要相同的空间,因此并集分配了两个字节。

    0000001100000010 little-endian 如果LSB存储在最低地址,则从union读取的int将为 0000001000000011 这是515的二进制代码。

    注意:即使int是32位检查,这也是正确的 Amnon's answer

        6
  •  3
  •   notacat    16 年前

    如果您在32位系统上,那么int是4个字节,但您只初始化了2个字节。访问未初始化的数据是未定义的行为。

    假设您在一个具有16位整数的系统上,那么您所做的仍然是实现定义的。如果您的系统是little endian,那么u.ch[0]将与u.i和u.ch的最低有效字节相对应 1 将是最重要的字节。在大端系统中,情况正好相反。此外,C标准并不强制实现使用 two's complement

    提示:如果使用十六进制值,更容易看到发生了什么。在小endian系统中,十六进制的结果将是0x0203。

    推荐文章