两个联合的算术,未定义的行为?

时间:2014-01-16 02:14:20

标签: c unions type-punning

#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添加了相同的指定数据类型,则它可以正常工作。 我以为 union 认为这是一个非问题?

3 个答案:

答案 0 :(得分:3)

从联盟的其他成员读取而不是您上次指定的成员会导致未指定的值。它并非无效,但标准没有规定如何解决类型惩罚,结果可能是陷阱重新反应..参见:

Is type-punning through a union unspecified in C99, and has it become specified in C11?

工会的目的不是允许打字。它允许您通过在两个不同的变量中重复使用相同的内存来节省空间,当您知道它们不会同时需要它们时。有用这个例子,请参阅:

How can a mixed data type (int, float, char, etc) be stored in an array?

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

答案 1 :(得分:2)

通过联合的类型惩罚已经legal since C89,因此没有未定义的行为,并且有几个编译器明确保证它会起作用,例如参见gcc documentation on type-punning。他们需要这个,因为在 C ++ 中它并不是那么明确。

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

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

var1.u的类型是 unsigned long long ,因此正确的格式说明符应该是%llu,而clang正好抱怨如下:

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

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

一旦你修复了我看到的输出( see it live ):

13838435755002691587

表明两个变量的变化都有效。

您看到的结果归因于IEEE 754 binary number的格式,如下所示:

enter image description here

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

  

3ff0 0000 0000 0002 16 ≈10000000000000004

     

c000 0000 0000 0000 16 = -2

因此,在您的情况下,为var1.f分配一个负数将设置至少一个高位。我们可以使用std::bitsetgcc轻松地在 C ++ 中进行探索,因为它们明确支持 C ++ 中的联合的类型惩罚:

#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;
}

我看到的结果是( see it live ):

13835058055282163712
1100000000000000000000000000000000000000000000000000000000000000
1100000000000000000000000000000000000000000000000000000000000000
-2.00000000000000133
1100000000000000000000000000000000000000000000000000000000000011
1100000000000000000000000000000000000000000000000000000000000011

答案 2 :(得分:1)

嗯。

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

坦率地说,可能使用此技巧进行某种转换(例如,如果您需要将一对机器字“转换”为单个双字),但每次使用此技术时应该清楚地了解目标CPU架构的血腥细节。

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

示例:

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

typedef union {
        float f;
        unsigned long long ull;
} csome;

int main(void) {
        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);
        return 0;
}
alex@galene ~/tmp $ cc -Wall -o test_union test_union.c
alex@galene ~/tmp $ ./test_union 
&f = 0xbfee4840, &ull = 0xbfee4840
cs2.ull = 1080033283

您可能会看到cs2.ull的值是“随机”