Концептуальная проблема в Союзе - PullRequest
8 голосов
/ 17 ноября 2010

Мой код такой

// 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?Я думал, что расположение памяти будет похоже на это .Что происходит с Values.iValue и Values.lValue;, когда я назначаю что-то для Values.dValue?

Ответы [ 4 ]

9 голосов
/ 17 ноября 2010

В union все элементы данных перекрываются. Вы можете использовать только один член данных за один раз.

iValue, lValue и dValue занимают одно и то же пространство.

Как только вы пишете в dValue, элементы iValue и lValue больше не могут использоваться: только dValue можно использовать.


Изменить: Чтобы ответить на комментарии ниже: Вы не можете записать в один член данных объединения, а затем прочитать из другого члена данных. Это приводит к неопределенному поведению. (Есть одно важное исключение: вы можете переинтерпретировать любой объект в C и C ++ как массив char. Существуют и другие незначительные исключения, например, возможность переинтерпретировать целое со знаком как беззнаковое целое. Стандарт C (C99 6.5 / 6-7) и Стандарт C ++ (C ++ 03 3.10, если я правильно помню).

Может ли эта "работа" практиковаться иногда? Да. Но если ваш компилятор прямо не заявит, что такая реинтерпретация гарантированно будет работать правильно и не определит поведение, которое он гарантирует, вы не можете на него полагаться.

7 голосов
/ 17 ноября 2010

Поскольку числа с плавающей запятой представлены иначе, чем целые числа.

Все эти переменные занимают одну и ту же область памяти (причем двойные занимают более очевидно).Если вы попытаетесь прочитать первые четыре байта этого двойного числа как int, вы не получите обратно то, что думаете.Вы имеете дело с необработанным макетом памяти, и вам нужно знать, как эти типы представлены.


РЕДАКТИРОВАТЬ: Я должен был также добавить (как уже отмечал Джеймс), что запись в одну переменную в объединении, а затем чтение из другой вызывает неопределенное поведение и его следует избегать (если вы не пере-интерпретация данных в виде массива символов).

2 голосов
/ 17 ноября 2010

Хорошо, давайте сначала посмотрим на более простой пример.Ответ Эда описывает плавающую часть, но как насчет того, чтобы проверить, как вначале хранятся целые числа и символы!

Вот пример, который я только что кодировал:

#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 .

В любом случае, я не эксперт в этом, просто кое-что, с чем я столкнулся, пытаясь декодировать некоторые двоичные файлы.Дело в том, что числа с плавающей точкой не обязательно кодируются так, как вы ожидаете ... вы должны понимать, как они хранятся внутри, чтобы понять, почему вы получаете этот вывод.

0 голосов
/ 17 ноября 2010

Вы сделали это:

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. Новое значение может показаться мусором, но это просто результат интерпретации первых байтов (sizeof int) двойного 3.1416, полагая, что там есть полезное значение 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

Важно отметить, что первая половина каждой строки показывает 32 бита, которые используются для iValue: обратите внимание, что двоичный код 1010 в младшем значащем байте (слева на процессоре Intel, подобном моему) равен 10 десятичному. Запись 3.1416 заменяет все 64-битные данные на шаблон, представляющий 3.1416 (см. http://en.wikipedia.org/wiki/Double_precision_floating-point_format). Старый шаблон 1010 перезаписывается, перекрывается, электромагнитная память не более.

...