У мене склалося враження, що доступ до іншого union
члена, окрім останнього набору, є UB, але я не можу знайти чітку довідку (крім відповідей, що стверджують, що це UB, але без підтримки стандарту).
Отже, чи не визначена поведінка?
У мене склалося враження, що доступ до іншого union
члена, окрім останнього набору, є UB, але я не можу знайти чітку довідку (крім відповідей, що стверджують, що це UB, але без підтримки стандарту).
Отже, чи не визначена поведінка?
Відповіді:
Плутанина полягає в тому, що C явно дозволяє натискати тип через союз, тоді як C ++ (c ++ 11) не має такого дозволу.
6.5.2.3 Структура та члени профспілки
95) Якщо член, який використовується для читання вмісту об'єкта об'єднання, не є тим самим, що член, який останній використовується для зберігання значення в об'єкті, відповідна частина представлення об'єкта значення переосмислюється як представлення об'єкта в новому тип, як описано в 6.2.6 (процес, який іноді називають '' типом покарання ''). Це може бути уявлення про пастку.
Ситуація з C ++:
9.5 Союзи [class.union]
У об'єднанні, щонайменше, один із нестатичних членів даних може бути активним у будь-який час, тобто значення, щонайменше, одного з нестатичних членів даних можуть бути збережені в об'єднанні в будь-який час.
Пізніше C ++ має мову, що дозволяє використовувати об'єднання, що містять struct
s із загальними початковими послідовностями; однак це не дозволяє накладати тип покарання.
Для того, щоб визначити , є чи об'єднання типу каламбурною це дозволено в C ++, ми повинні шукати далі. Нагадаємо, щоc99 є нормативним посиланням для C ++ 11 (і C99 має схожу мову з C11, що дозволяє накладати тип профспілки):
3.9 Типи [basic.types]
4 - Об'єктне представлення об'єкта типу T - це послідовність N непідписаних символів char, взятих об'єктом типу T, де N дорівнює sizeof (T). Представлення значення об'єкта - це набір бітів, що містять значення типу T. Для тривіально копіюваних типів представлення значення - це набір бітів у поданні об'єкта, що визначає значення, яке є одним дискретним елементом реалізації- визначений набір значень. 42
42) Ціль полягає в тому, щоб модель пам'яті C ++ була сумісною з мовою програмування ISO / IEC 9899 C.
Це стає особливо цікавим, коли ми читаємо
3.8 Тривалість життя об'єкта [basic.life]
Термін експлуатації об’єкта типу T починається тоді, коли: - отримано сховище з належним вирівнюванням і розміром для типу T, і - якщо об’єкт має нетривіальну ініціалізацію, його ініціалізація завершена.
Отже, для примітивного типу (який ipso facto має тривіальну ініціалізацію), що міститься в об'єднанні, термін експлуатації об'єкта включає принаймні термін експлуатації самого об'єднання. Це дозволяє нам посилатися
3.9.2 Типи сполук [basic.compound]
Якщо об’єкт типу T розташований за адресою A, вказівник типу cv T *, значення якого є адресою A, вказує на цей об’єкт, незалежно від того, як отримано значення.
Якщо припустити, що операція, яка нас цікавить, - це тип-покарання, тобто взяття значення неактивного члена профспілки, і з огляду на вищевикладене, що у нас є дійсне посилання на об'єкт, на який посилається цей член, ця операція має значення " -перерахунок величини:
4.1 Перетворення значення на реальне значення [conv.lval]
Glvalue нефункціонального типу без масиву
T
може бути перетворений у prvalue. ЯкщоT
це неповний тип, програма, яка потребує цього перетворення, неправильно сформована. Якщо об’єкт, на який посилається glvalue, не є об'єктом типуT
і не є об'єктом типу, похідним від ньогоT
, або якщо об'єкт неініціалізований, програма, яка потребує цього перетворення, не визначає поведінку.
Тоді питання полягає в тому, чи ініціалізується об'єкт, який є неактивним членом союзу, зберіганням до активного члена об'єднання. Наскільки я можу сказати, це не так, і хоча, якщо:
char
сховище масивів і назад (3.9: 2), абодоступ до об'єднання неактивним членом визначається і визначається таким чином, щоб слідкувати за представленням об'єкта та значення, доступ без одного з перерахованих вище вкладень є невизначеною поведінкою. Це має наслідки для оптимізацій, дозволених виконувати в такій програмі, оскільки, звичайно, реалізація може припускати, що не визначена поведінка не відбувається.
Тобто, хоча ми можемо законно сформувати значення для неактивного члена профспілки (саме тому привласнення до неактивного члена без будівництва нормально), це вважається неініціалізованим.
memcpy
реалізації (доступ до об'єктів за допомогою unsigned char
lvalues), забороняє доступ до *p
after int *p = 0; const int *const *pp = &p;
(навіть незважаючи на те, що неявна конверсія з int**
в, const int*const*
є дійсною), забороняє навіть доступ c
після struct S s; const S &c = s;
. Випуск CWG 616 . Чи дозволяє це нове формулювання? Там також [basic.lval].
&
означає одиночний оператор, звернувшись до члена профспілки. Я думаю, що отриманий вказівник повинен бути придатним для доступу до члена принаймні до наступного прямого або непрямого використання будь-якого іншого члена lvalue, але в gcc вказівник не є корисним навіть настільки довго, що викликає питання про те, що передбачається, &
оператор.
Стандарт C ++ 11 говорить про це так
9.5 Союзи
У об'єднанні, щонайменше, один із нестатичних членів даних може бути активним у будь-який час, тобто значення, щонайменше, одного з нестатичних членів даних можуть бути збережені в об'єднанні в будь-який час.
Якщо зберігається лише одне значення, як можна прочитати інше? Просто його немає.
Документація gcc перераховує це в розділі, визначеному реалізацією
- Доступ до об'єкта об'єднання доступний за допомогою члена іншого типу (C90 6.3.2.3).
Відповідні байти представлення об'єкта трактуються як об'єкт типу, що використовується для доступу. Див. Розділ Типовий набір Це може бути уявлення про пастку.
що вказує на те, що цього не вимагає стандарт C.
2016-01-05: Через коментарі я був пов'язаний зі звітом про дефект C99 № 283, який додає аналогічний текст, як виноска до стандартного документа C:
78a) Якщо член, який використовується для доступу до вмісту об'єкта об'єднання, не є тим самим, як член, який останній використовується для зберігання значення в об'єкті, відповідна частина представлення об'єкта значення переінтерпретується як представлення об'єкта в новому тип, як описано в 6.2.6 (процес, який іноді називають "типом накачування"). Це може бути уявлення про пастку.
Не впевнений, чи багато це уточнює, враховуючи, що виноска не є нормативною для стандарту.
Я думаю, що найближчим до стандарту доводиться те, що це невизначена поведінка - це те, де воно визначає поведінку для об'єднання, що містить загальну початкову послідовність (C99, §6.5.2.3 / 5):
Одна спеціальна гарантія робиться з метою спрощення використання об'єднань: якщо об'єднання містить кілька структур, які мають спільну початкову послідовність (див. Нижче), і якщо об'єкт об'єднання в даний час містить одну з цих структур, дозволено перевіряти загальну початкова частина будь-якого з них де завгодно, що видно декларацію про повний тип союзу. Дві структури мають спільну початкову послідовність, якщо відповідні члени мають сумісні типи (і для бітових полів однакові ширини) для послідовності одного або більше початкових членів.
C ++ 11 дає подібні вимоги / дозвіл у § 9.2 / 19:
Якщо об'єднання стандартного макета містить дві або більше структур стандартного макету, які мають спільну початкову послідовність, і якщо об'єкт об'єднання стандартного макета в даний час містить одну з цих структур стандартного компонування, дозволяється перевіряти загальну початкову частину будь-якої їх. Дві структури стандартного компонування мають спільну початкову послідовність, якщо відповідні члени мають типи сумісних з компонуванням і або жоден член не є бітовим полем, або обидва є бітовими полями однакової ширини для послідовності одного або декількох початкових членів.
Хоча обидва не заявляють це прямо, вони обидва несуть серйозне значення, що "перевірка" (читання) члена "дозволена" лише якщо 1) це (частина) останнього написаного члена, або 2) є частиною загальної початкової послідовність.
Це не пряме твердження, що діяти в іншому випадку є невизначеною поведінкою, але це найближче, про що я знаю.
union
покарання, коли я не визначив, я почув себе невідомим, оскільки в конкретному блозі було враження, що це все в порядку, і створив навколо себе кілька великих структур та проектів. Тепер я думаю, що я можу бути все в порядку, оскільки мої union
s містять класи, що мають однакові типи в передній частині
union
містить, наприклад, a uint8_t
і a class Something { uint8_t myByte; [...] };
- я б припустив, що це застереження також буде застосовано тут, але це сформульовано дуже навмисно, щоб допустити лише struct
s. На щастя, я вже використовую такі замість сирих примітивів: O
Щось ще не згадується в доступних відповідях - це виноска 37 в пункті 21 розділу 6.2.5:
Зауважте, що агрегатний тип не включає тип об'єднання, оскільки об'єкт із типом об'єднання може містити лише одного члена за раз.
Ця вимога, очевидно, означає, що ви не повинні писати в члені та читати в іншому. У цьому випадку це може бути невизначена поведінка через відсутність конкретизації.
Я добре пояснюю це на прикладі.
припустимо, у нас є такий союз:
union A{
int x;
short y[2];
};
Я добре припускаю, що sizeof(int)
дає 4, а це sizeof(short)
дає 2.,
коли ви пишете, union A a = {10}
що добре створити новий var типу A, вклавши в нього значення 10.
Ваша пам'ять має виглядати так: (пам’ятайте, що всі члени профспілки мають одне і те саме місце)
| х | | y [0] | у [1] | ----------------------------------------- a-> | 0000 0000 | 0000 0000 | 0000 0000 | 0000 1010 | -----------------------------------------
як ви могли бачити, значення ax 10 - значення ay 1 - 10, а ay [0] - 0.
Тепер, що добре, якщо я це роблю?
a.y[0] = 37;
наша пам'ять буде виглядати так:
| х | | y [0] | у [1] | ----------------------------------------- a-> | 0000 0000 | 0010 0101 | 0000 0000 | 0000 1010 | -----------------------------------------
це перетворить значення сокири на 2424842 (у десятковій кількості).
Тепер, якщо у вашому союзі є плаваюча або подвійна, ваша карта пам’яті буде більше безладдя через те, як ви зберігаєте точні цифри. Більше інформації ви можете отримати тут .