Член Союза С++ не инициализирован

Я только начал свои поиски профсоюзов, когда обнаружил кое-что странное.

Если я запущу эту программу

    #include <iostream>
    using namespace std;
    union myun{
    public:
    int x;
    char c;
    };

    int main()
    {
     myun y;
     //y.x=65;
      y.c='B';
     cout<<y.x;
    }

На выходе было какое-то мусорное значение, которое не изменится, если изменить значение y.c. Далее я сделал это

    #include <iostream>
    using namespace std;
    union myun{
    public:
    int x;
    char c;

    };
     int main()
     {
      myun y;
      y.x=65;
      y.c='B';
      cout<<y.x;
     }

Результат, как и ожидалось, будет 66, потому что y.c='B' заменяет 65 своим значением ASCII (66). Кто-нибудь может объяснить первый случай?


person Shashank    schedule 12.10.2012    source источник
comment
Чтение с одного после установки другого - UB, IIRC.   -  person chris    schedule 12.10.2012


Ответы (7)


На самом деле это неопределенное поведение для чтения из члена союза, который не был последним.

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

Из стандарта С++ 03 (теперь он заменен С++ 11, но все еще актуален):

В объединении в любой момент времени может быть активен не более одного члена данных, то есть значение не более чем одного из членов данных может быть сохранено в объединении в любой момент времени.

Я думаю, вы можете заглянуть в reinterpret_cast, если вы хотите заниматься подобным наложением.


С точки зрения того, что на самом деле происходит под обложками в первом, шестнадцатеричное значение вывода числа:

-1218142398 (signed) -> 3076824898 (unsigned) -> B7649F42 (hex)
                                                       ==
                                                       ^^
                                                       ||
                                       0x42 is 'B' ----++

должен дать подсказку. y.c='B' устанавливает только один байт этой структуры, оставляя остальные три байта (в моем случае) неопределенными.

Ввод строки y.x=65 перед этой точкой устанавливает все четыре байта, а эти три запасных устанавливаются равными нулю. Следовательно, они остаются равными нулю, когда вы устанавливаете один байт в следующем назначении.

person paxdiablo    schedule 12.10.2012
comment
Это неопределенное поведение не только из-за правила об активных членах в объединении, но и если сохранение одного члена и чтение другого члена должны были получить доступ к значению хранимого члена, то это было бы строгим нарушением псевдонимов. Кроме того, я не вижу ничего в стандарте, в котором упоминалось бы, что объекты разных типов совместимы с макетом, если они имеют одинаковую ширину, поэтому я не думаю, что если бы char и int были одинаковой ширины, они были бы макетом совместимый. - person bames53; 12.10.2012
comment
static_cast? Вы имели в виду reinterpret_cast? - person David Rodríguez - dribeas; 12.10.2012
comment
Извини, Дэвид, ты абсолютно прав, исправил. bames53, я не говорил, что это сделает их layout_совместимыми, я сказал, что может это вероятно, так как и int, и char являются арифметическими типами, но я не уверен, что это гарантировано . - person paxdiablo; 12.10.2012

Ну, вы как бы объяснили первый случай, когда продемонстрировали свое понимание второго случая.

Инициализация символьной части изменяет только один байт в типе данных, который предоставляет int. Предполагая 32-битный int, это означает, что 3 байта все еще не инициализированы... Отсюда и мусор.

Вот использование памяти вашего союза:

              byte
           0  1  2  3
         +------------
myun::x  | X  X  X  X
myun::c  | X  -  -  -

Когда вы устанавливаете x, вы устанавливаете целое число, поэтому все оставшиеся байты инициализируются. Когда вы устанавливаете c, вы изменяете только один байт.

person paddy    schedule 12.10.2012

  y.c='B';
 cout<<y.x;

Это имеет неопределенное поведение. В любой момент времени союз содержит только одного из своих членов. Вы не можете пытаться прочитать элемент int, если он действительно содержит элемент char. Поскольку поведение этого не определено, компилятору разрешено делать с кодом все, что он хочет.

person R. Martinho Fernandes    schedule 12.10.2012

Потому что sizeof(int) != sizeof(char).

Другими словами, целое число и символ занимают разное количество памяти (в современных компьютерах int занимает 4 байта, char — 1 байт). Профсоюз настолько велик, насколько велик его самый большой член. Таким образом, когда вы устанавливаете char, вы устанавливаете только 1 байт памяти — остальные 3 байта — просто случайный мусор.

Либо сначала установите самый большой член союза, либо сделайте что-то вроде:

memset(&y, 0, sizeof(y));

чтобы заполнить весь союз нулем.

person Michael Kohne    schedule 12.10.2012

В объединении выделенная память равна размеру самого большого члена, который в вашем случае равен int, т.е. 2 байта в случае 16-битного компилятора. Все элементы используют одно и то же пространство памяти для хранения своих данных, поэтому практически одновременно может храниться только один тип элементов.

Когда вы присвоили значение «B» элементу char, он сохранил 66 в своем пространстве памяти размером 1 байт. Затем вы попытались вывести значение члена int, который, однако, попытался вычислить значение, прочитав значения из 2 байтов памяти, поэтому вы получили значение мусора.

person asheeshr    schedule 12.10.2012

Локальные переменные (точнее, переменные в стеке, т.е. имеющие класс хранения «автоматический») типа POD не инициализируются ничем при их объявлении, поэтому 3 байта (или 7 байтов в 64-битной системе) не затрагиваются ваше задание на y.c будет содержать случайный мусор.

Также обратите внимание, что конкретный байт, на который влияет назначение y.c, зависит от порядка следования байтов ЦП, поэтому этот код будет вести себя по-разному в разных системах, даже если вы инициализируете y.x перед назначением y.c.

person j_random_hacker    schedule 12.10.2012

Переменная y относится к типу union, а длина y составляет четыре байта. Например, схема памяти y выглядит так:

---------------------------------
| byte1 | byte2 | byte3 | byte4 |
---------------------------------

1) В первой программе предложение y.c='B'; просто устанавливает байт1, но байт2, байт3, байт4 являются случайными значениями в стеке.

2) Во второй программе предложение y.x=65; установите байт1 как 65 , байт2, байт3, байт4 равны нулю. Затем предложение y.c='B'; устанавливает byte1 как целочисленное значение ASCII 'B', что дает результат 66.

person bbg    schedule 12.10.2012