联盟中的概念问题

时间:2010-11-17 04:33:22

标签: c++ c unions

我的代码就是这个

// using_a_union.cpp
#include <stdio.h>

union NumericType
{
    int         iValue;
    long        lValue;  
    double      dValue;  
};

int main()
{
    union NumericType Values = { 10 };   // iValue = 10
    printf("%d\n", Values.iValue);
    Values.dValue = 3.1416;
    printf("%d\n", Values.iValue); // garbage value
}

为什么在执行Values.iValue后尝试打印Values.dValue = 3.1416时会出现垃圾值? 我以为内存布局就像thisValues.iValueValues.lValue;会发生什么 我为Values.dValue分配内容时{{1}}?

4 个答案:

答案 0 :(得分:9)

union中,所有数据成员都重叠。您一次只能使用一个联合的一个数据成员。

iValuelValuedValue占据相同的空间。

只要您写信至dValueiValuelValue成员就不再可用:只有dValue可用。


编辑:要解决以下注释:您不能写入联合的一个数据成员,然后从另一个数据成员读取。这样做会导致未定义的行为。 (有一个重要的例外:您可以将C和C ++中的任何对象重新解释为char的数组。还有其他一些小的例外,例如能够将有符号整数重新解释为无符号整数。)您可以找到更多在C标准(C99 6.5 / 6-7)和C ++标准(C ++ 03 3.10,如果我没记错的话)。

在某些时候可能会在实践中“工作”吗?是。但除非你的编译器明确声明这种重新解释能够保证正常工作并指定它保证的行为,否则你不能依赖它。

答案 1 :(得分:7)

因为floating point numbers are represented differently than integers are.

所有这些变量占据相同的内存区域(双重占用更明显)。如果您尝试将该双精度的前四个字节读作int,那么您将无法获得您的想法。您正在处理原始内存布局,您需要知道这些类型的表示方式。


编辑:我还应该添加(正如James已经指出的那样)在一个union中写入一个变量然后从另一个变量读取会调用未定义的行为并且应该避免(除非你将数据重新解释为char数组。

答案 2 :(得分:2)

好吧,让我们先看一下更简单的例子。 Ed的回答描述了浮动部分,但是我们如何检查内联和字符是如何存储的呢!

这是我刚刚编写的一个例子:

#include "stdafx.h"
#include <iostream>
using namespace std;

union Color {
    int value;
    struct {
        unsigned char R, G, B, A;
    };
};

int _tmain(int argc, _TCHAR* argv[])
{
    Color c;
    c.value = 0xFFCC0000;
    cout << (int)c.R << ", " << (int)c.G << ", " << (int)c.B << ", " << (int)c.A << endl;
    getchar();
    return 0;
}

您对输出的期望是什么?

  

255,204,0,0

右?

如果int是32位,并且每个字符都是8位,那么R应该对应于最左边的字节,G应该对应于第二个字节,依此类推。

但那是错的。至少在我的机器/编译器上,它显示的是以反向字节顺序存储的。我明白了,

  

0,0,204,255

因此,为了使这给出我们期望的输出(或者我预期的输出),我们必须将结构更改为A,B,G,R。这与endianness

有关

无论如何,我不是这方面的专家,只是我在尝试解码一些二进制文件时偶然发现的东西。关键是,浮点数不一定按照你期望的方式进行编码......你必须了解它们如何在内部存储以理解为什么你得到那个输出。

答案 3 :(得分:0)

您已完成此操作:

union NumericType Values = { 10 };   // iValue = 10 
printf("%d\n", Values.iValue); 
Values.dValue = 3.1416; 

编译器如何为此联合使用内存类似于使用具有最大大小和对齐的变量(如果有多个则使用其中任何一个),并在写入/访问联合中的其他类型之一时重新解释转换,如在:

double dValue; // creates a variable with alignment & space
               // as per "union Numerictype Values"
*reinterpret_cast<int*>(&dValue) = 10; // separate step equiv. to = { 10 }
printf("%d\n", *reinterpret_cast<int*>(dValue)); // print as int
dValue = 3.1416;                                 // assign as double
printf("%d\n", *reinterpret_cast<int*>(dValue));  // now print as int

问题在于,在将dValue设置为3.1416时,您已经完全覆盖了用于保存数字10的位。新值可能看起来很垃圾,但它只是解释double 3.1416的first(sizeof int)字节,相信那里有一个有用的int值。

如果您希望这两件事情是独立的 - 那么设置double并不会影响先前存储的int - 那么您应该使用struct / class

可能会帮助您考虑此计划:

#include <iostream>

void print_bits(std::ostream& os, const void* pv, size_t n)
{
    for (int i = 0; i < n; ++i)
    {
        uint8_t byte = static_cast<const uint8_t*>(pv)[i];
        for (int j = 0; j < 8; ++j)
            os << ((byte & (128 >> j)) ? '1' : '0');
        os << ' ';
    }
}

union X
{
    int i;
    double d;
};

int main()
{
    X x = { 10 };
    print_bits(std::cout, &x, sizeof x);
    std::cout << '\n';
    x.d = 3.1416;
    print_bits(std::cout, &x, sizeof x);
    std::cout << '\n';
}

对我来说,产生了这个输出:

00001010 00000000 00000000 00000000 00000000 00000000 00000000 00000000 
10100111 11101000 01001000 00101110 11111111 00100001 00001001 01000000

至关重要的是,每一行的前半部分显示了用于iValue的32位:请注意,最低有效字节中的1010二进制文件(在像我这样的Intel CPU的左侧)是十进制的10。写入3.1416会将整个64位更改为表示3.1416的模式(请参阅http://en.wikipedia.org/wiki/Double_precision_floating-point_format)。旧的1010模式被覆盖,被破坏,不再是电磁存储器。