Членът на C++ Union не е инициализиран

Тъкмо започвах търсенето си със синдикатите, когато открих нещо странно

Ако пусна тази програма

    #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 (по-правилно, може е така, ако тези два типа имат сходни битови ширини, но това обикновено не е така).

От стандарта C++03 (заменен от C++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_compatible, казах, че може. Това е вероятно, тъй като и int, и char са аритметични типове, но не съм сигурен, че е гарантирано . - person paxdiablo; 12.10.2012

Е, донякъде обяснихте първия случай, когато показахте разбирането си за втория случай.

Инициализирането на символната част променя само един байт в тип данни, който предоставя int. Ако приемем 32-bit 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, зависи от endianness на CPU, така че този код ще се държи различно на различните системи, дори ако инициализирате 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; задайте byte1 като 65, byte2, byte3, byte4 е нула. След това изречението y.c='B'; задава byte1 като цяло число ASCII стойност на 'B', като по този начин дава резултат 66.

person bbg    schedule 12.10.2012