2
#include <stdio.h>
#include <stdlib.h>

typedef union
{

    double f;

    unsigned long long u;

    int long long i;
} r;

int main()
{
  r var1, var2;

  var1.f = -3.5;
  var2.u = 3;

  var1.u = var1.u + var2.u;

  printf("%u", var1.u);
  return 0;
}

为什么这只返回 var1 的值而不是总和?如果var1var2添加了相同的分配数据类型,它就可以工作。我认为工会使这成为一个非问题?

4

3 回答 3

3

从与您上次分配的成员不同的联合成员中读取会导致未指定的值。它不是无效的,但是标准没有指定如何解决类型双关语,结果可能是陷阱表示。请参阅:

是否通过 C99 中未指定的联合进行类型双关,并且它是否已在 C11 中指定?

工会的目的不是允许类型双关语。当您知道您永远不会同时需要它们时,它允许您通过为两个不同的变量重用相同的内存来节省空间。有关这有用的示例,请参见:

如何将混合数据类型(int、float、char 等)存储在数组中?

(这恰好是我得票最高的答案)。

于 2014-01-16T02:18:02.950 回答
2

自 C89 以来,通过联合进行类型双关一直是合法的,因此那里没有未定义的行为,并且一些编译器明确保证它会起作用,例如,请参阅关于类型双关的 gcc 文档。他们需要这个,因为在C++中它不是那么明确。

但是这条线确实有未定义的行为:

printf("%u", var1.u);

的类型var1.uunsigned long long,因此正确的格式说明符应该是%llu,并clang适当地抱怨如下:

warning: format specifies type 'unsigned int' but the argument has type 'unsigned long long' [-Wformat]

printf("%u", var1.u);
        ~~   ^~~~~~
        %llu

一旦你修复了我看到的输出是这样的(现场查看):

13838435755002691587

这表明两个变量的变化都产生了影响。

您看到的结果是由于IEEE 754 二进制数的格式,如下所示:

在此处输入图像描述

这是显示数字的十六进制表示的几个示例之一:

3ff0 0000 0000 0002 16 ≈ 1.0000000000000004

c000 0000 0000 0000 16 = –2

因此,在您的情况下,分配一个负数var1.f将设置至少一个高位。我们可以使用std::bitset在C++中轻松探索这一点,因为它们通过C++中的联合明确支持类型双关:gcc

#include <iostream>
#include <iomanip>
#include <bitset>
#include <string>

typedef union
{
    double f;
    unsigned long long u;
    int long long i;
} r;

int main() 
{
    r var1, var2;

    var1.f = -2 ; // High bits will be effected so we expect a large number for u
                  // Used -2 since we know what the bits should look like from the
                  // example in Wikipedia
    std::cout << var1.u << std::endl ;

    std::bitset<sizeof(double)*8> b1( var1.u ) ;
    std::bitset<sizeof(double)*8> b2( 13835058055282163712ull ) ;

    std::cout << b1 << std::endl ;
    std::cout << b2 << std::endl ;

    var2.u = 3;

    var1.u = var1.u + var2.u; // Low bits will be effected so we expect a fraction
                              // to appear in f

    std::cout << std::fixed << std::setprecision(17) <<  var1.f << std::endl ;

    std::bitset<sizeof(double)*8> b3( var1.u ) ;
    std::bitset<sizeof(double)*8> b4( 13835058055282163715ull ) ;

    std::cout << b3 << std::endl ;
    std::cout << b4 << std::endl ;

    return 0;
}

我看到的结果是(现场观看):

13835058055282163712
1100000000000000000000000000000000000000000000000000000000000000
1100000000000000000000000000000000000000000000000000000000000000
-2.00000000000000133
1100000000000000000000000000000000000000000000000000000000000011
1100000000000000000000000000000000000000000000000000000000000011
于 2014-01-16T03:24:17.733 回答
1

嗯。

在一个联合中,字段驻留在同一个物理空间上。也就是说,union的大小大致是它最大的field的大小。您分配给联合的浮点字段,然后尝试用作整数值。这会导致未定义的行为,更准确地说,这种行为取决于目标平台上整数和浮点数的表示。

坦率地说,您可能会在某些类型的转换中使用此技巧(例如,如果您需要将一对机器字“转换”为单个 dword),但每次使用此技术时,您都应该清楚地了解目标 CPU 架构的血腥细节.

我的一个朋友曾经在 SPARC 计算机上遇到虚假的段错误,因为他试图使用类似的技术访问未对齐的数据 :)

这个例子:

alex@galene ~/tmp $ cat test_union.c
#include <stdio.h>

typedef联合{
        浮动 f;
        unsigned long long ul;
} csome;

诠释主要(无效){
        csome cs;
        csome cs2;
        printf("&f = %p, &ull = %p\n", &cs.f, &cs.ull);
        cs.f = 3.5;
        cs2.ull = 3;
        cs2.ull = cs.ull + cs2.ull;
        printf("cs2.ull = %Ld\n", cs2.ull);
        返回0;
}
alex@galene ~/tmp $ cc -Wall -o test_union test_union.c
亚历克斯@galene ~/tmp $ ./test_union
&f = 0xbfee4840,&ull = 0xbfee4840
cs2.ull = 1080033283

如您所见,值为cs2.ull“随机”

于 2014-01-16T02:19:30.983 回答