Доступ к неактивному члену профсоюза и поведение undefined?

У меня создалось впечатление, что доступ к члену union, отличному от последнего установленного, является UB, но я не могу найти твердую ссылку (кроме ответов, требующих ее UB, но без поддержки стандарта).

Итак, это поведение undefined?

Ответы

Ответ 1

Смятение состоит в том, что C явно разрешает ввод слов через объединение, тогда как С++ () не имеет такого разрешения.

6.5.2.3 Структура и элементы объединения

95) Если элемент, используемый для чтения содержимого объекта объединения, не совпадает с элементом, который последний раз использовался для сохранить значение в объекте, соответствующая часть представления объекта значения переинтерпретируется как представление объекта в новом типе, как описано в 6.2.6 (процесс, иногда называемый "типом" каламбурная). Это может быть ловушечное представление.

Ситуация с С++:

9.5 Союзы [class.union]

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

С++ позже имеет язык, разрешающий использование объединений, содержащих struct, с общими исходными последовательностями; это, однако, не позволяет печатать по типу.

Чтобы определить, разрешен ли тип union-punning на С++, мы должны искать дальше. Напомним, что является нормативной ссылкой для С++ 11 (и C99 имеет схожий язык с C11, разрешающим объединение типа-punning):

3.9 Типы [basic.types]

4 - Объектное представление объекта типа T является последовательностью N неподписанных char объектов, рассмотренных объект типа T, где N равно sizeof (T). Представление значения объекта - это набор бит, который удерживайте значение типа T. Для тривиально копируемых типов представление значений представляет собой набор бит в объекте представление, которое определяет значение, которое является одним дискретным элементом определенного для реализации набора значения. 42
42) Предполагается, что модель памяти С++ совместима с моделью языка программирования ISO/IEC 9899.

Это особенно интересно, когда мы читаем

3.8 Время жизни объекта [basic.life]

Время жизни объекта типа T начинается, когда: - получено хранилище с надлежащим выравниванием и размером для типа T и - если объект имеет нетривиальную инициализацию, его инициализация завершена.

Итак, для примитивного типа (который ipso facto имеет тривиальную инициализацию), содержащегося в объединении, время жизни объекта охватывает, по крайней мере, время жизни самого объединения. Это позволяет нам ссылаться на

3.9.2 Типы соединений [basic.compound]

Если объект типа T расположен по адресу A, указатель типа cv T *, значением которого является адрес A указывает на этот объект, независимо от того, как было получено значение.

Предполагая, что операция, в которой мы заинтересованы, представляет собой тип-punning, т.е. принимая значение неактивного члена профсоюза, и дается в соответствии с вышеизложенным, что мы имеем действительную ссылку на объект, на который ссылается этот член, Преобразование lvalue-to-rvalue:

4.1 Преобразование Lvalue-to-rval [conv.lval]

Значение gl-значения нефункционного типа без массива T может быть преобразовано в prvalue. Если T является неполным типом, программа, которая требует этого преобразования, плохо сформирована. Если объект, к которому относится glvalue, не является объектом типа T и не является объектом типа, полученного из T, или если объект не инициализирован, программа, которая требует этого преобразования, имеет неопределенное поведение.

Затем возникает вопрос, инициализируется ли объект, который является неактивным членом объединения, хранилищем активному члену объединения. Насколько я могу судить, это не так и так, хотя если:

  • объединение копируется в хранилище массива char и обратно (3.9: 2) или
  • объединение по очереди копируется в другое объединение одного типа (3.9: 3) или
  • соединение осуществляется через языковые границы программным элементом, соответствующим ISO/IEC 9899 (насколько это определено) (3.9: 4 примечание 42), затем

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

То есть, хотя мы можем законно сформировать lvalue для неактивного члена объединения (поэтому назначение неактивного элемента без построения в порядке) считается неинициализированным.

Ответ 2

Стандарт С++ 11 говорит об этом так:

9.5 Союзы

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

Если хранится только одно значение, как вы можете читать другое? Его просто нет.


Документация gcc перечисляет это в Определенное поведение реализации

  • Доступ к члену объекта union осуществляется с использованием члена другого типа (C90 6.3.2.3).

Соответствующие байты представления объекта рассматриваются как объект типа, используемого для доступа. См. "Тип-караун". Это может быть ловушечное представление.

что означает, что это не требуется стандартом C.


2016-01-05: через комментарии я был связан с C99 Defect Report # 283, который добавляет аналогичный текст в виде сноски к C:

78a) Если член, используемый для доступа к содержимому объекта объединения, не совпадает с элементом, который последний раз использовался для хранения значения в объекте, соответствующая часть представления объекта значения интерпретируется как представление объекта в новом типе, как описано в 6.2.6 (процесс, иногда называемый "пингом типа" ). Это может быть ловушечное представление.

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

Ответ 3

Я думаю, что самый близкий стандарт приходит к утверждению, что поведение undefined - это то, где он определяет поведение для объединения, содержащего общую начальную последовательность (C99, §6.5.2.3/5):

Для упрощения использования союзов предусмотрена одна специальная гарантия: если соединение содержит несколько структур, которые имеют общую начальную последовательность (см. ниже), и если объединение объект в настоящее время содержит одну из этих структур, разрешено проверять общие начальной части любого из них в любом месте, где декларация полного типа объединения видимый. Две структуры имеют общую начальную последовательность, если соответствующие члены имеют совместимые типы (и для бит-полей, одинаковые ширины) для последовательности одного или нескольких начальные элементы.

С++ 11 дает аналогичные требования/разрешения в §9.2/19:

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

Хотя ни один из них не является прямым, эти оба имеют сильное значение, что "проверка" (чтение) члена "разрешена" только в том случае, если 1) это (часть) члена, который был недавно написан, или 2) является частью общая начальная последовательность.

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

Ответ 4

Что-то, что еще не упоминается в доступных ответах, - это сноска 37 в пункте 21 раздела 6.2.5:

Обратите внимание, что тип агрегата не включает тип объединения, потому что объект с типом объединения может содержать только один элемент за раз.

Это требование явно означает, что вы не должны писать в члене и читать в другом. В этом случае это может быть поведение undefined из-за отсутствия спецификации.

Ответ 5

Я хорошо объясню это на примере.
предположим, что мы имеем следующий союз:

union A{
   int x;
   short y[2];
};

Я полагаю, что sizeof(int) дает 4, а sizeof(short) дает 2.
когда вы пишете union A a = {10}, чтобы создать новый var типа A, введите в него значение 10.

ваша память должна выглядеть так: (помните, что все члены профсоюза получают одно и то же местоположение)

       |                   x                   |
       |        y[0]       |       y[1]        |
       -----------------------------------------
   a-> |0000 0000|0000 0000|0000 0000|0000 1010|
       -----------------------------------------

как вы могли видеть, значение ax равно 10, значение ay 1 равно 10, а значение ay [0] 0.

теперь, что хорошо, если я это сделаю?

a.y[0] = 37;

наша память будет выглядеть так:

       |                   x                   |
       |        y[0]       |       y[1]        |
       -----------------------------------------
   a-> |0000 0000|0010 0101|0000 0000|0000 1010|
       -----------------------------------------

это превратит значение a.x в 2424842 (в десятичной форме).

теперь, если ваш союз имеет float или double, ваша карта памяти будет скорее беспорядочной, потому что вы храните точные числа. более подробную информацию вы можете получить в здесь.