Я раньше использовал союзы с комфортом; Сегодня я был встревожен, когда я прочитал этот пост и узнал, что этот код
union ARGB
{
uint32_t colour;
struct componentsTag
{
uint8_t b;
uint8_t g;
uint8_t r;
uint8_t a;
} components;
} pixel;
pixel.colour = 0xff040201; // ARGB::colour is the active member from now on
// somewhere down the line, without any edit to pixel
if(pixel.components.a) // accessing the non-active member ARGB::components
фактически является неопределенным поведением, т. е. чтение из члена объединения, отличного от недавно записанного, приводит к неопределенному поведению. Если это не предполагаемое использование союзов, что это? Кто-нибудь может объяснить это подробно?
Обновить:
Я хотел бы уточнить несколько вещей в ретроспективе.
- Ответ на вопрос не одинаков для C и C ++; моя неосведомленная младшая личность пометила его как C и C ++.
- После изучения стандарта C ++ 11 я не мог окончательно сказать, что он призывает к доступу / проверке неактивного члена объединения не определено / не определено / определяется реализацией. Все, что я мог найти, было §9.5 / 1:
Если объединение стандартной компоновки содержит несколько структур стандартной компоновки, которые имеют общую начальную последовательность, и если объект этого типа объединения стандартной компоновки содержит одну из структур стандартной компоновки, разрешается проверять общую начальную последовательность любой членов структуры стандартного макета. §9.2 / 19: Две структуры стандартного макета разделяют общую начальную последовательность, если соответствующие элементы имеют типы, совместимые с макетом, и ни один из элементов не является битовым полем, либо оба являются битовыми полями с одинаковой шириной для последовательности из одного или более начальных члены.
- В то время как в C ( C99 TC3 - DR 283 и далее) это допустимо ( спасибо Паскалю Куоку за то, что поднял этот вопрос). Однако попытка сделать это может привести к неопределенному поведению , если прочитанное значение окажется недопустимым (так называемое «представление ловушки») для типа, через которое оно читается. В противном случае значение read определяется реализацией.
В C89 / 90 это объясняется неопределенным поведением (Приложение J), а в книге K & R говорится, что реализация определена. Цитата из K & R:
Это цель объединения - единственная переменная, которая может на законных основаниях содержать любой из нескольких типов. [...], пока использование является последовательным: извлеченный тип должен быть последним сохраненным типом. Программист обязан следить за тем, какой тип в данный момент хранится в объединении; результаты зависят от реализации, если что-то хранится как один тип и извлекается как другой.
Выписка из ТС ++ PL Страуструпа (выделено мое)
Использование союзов может иметь важное значение для совместимости данных, [...] иногда неправильно используемых для «преобразования типов ».
Прежде всего, этот вопрос (название которого остается неизменным со времени моего запроса) был задан с намерением понять цель объединения, а не то, что стандарт позволяет, например, Использование наследования для повторного использования кода, конечно, разрешено стандартом C ++, но это не было целью или первоначальным намерением ввести наследование как особенность языка C ++ . По этой причине ответ Андрея продолжает оставаться принятым.
источник
b, g, r,
иa
может не быть смежным, и, следовательно, не соответствовать макетуuint32_t
. Это в дополнение к проблемам Endianess, на которые указывали другие.scouring C++11's standard I couldn't conclusively say that it calls out accessing/inspecting a non-active union member is undefined [...] All I could find was §9.5/1
...действительно? Вы цитируете примечание об исключении , а не главное в самом начале абзаца : «В объединении не более одного элемента не статических данных может быть активным в любое время, то есть значение не более одного из Нестатические члены данных могут быть сохранены в объединении в любое время. " - и вплоть до p4: «В общем, нужно использовать явные вызовы деструктора и размещение новых операторов для изменения активного члена объединения »Ответы:
Цель профсоюзов довольно очевидна, но по некоторым причинам люди часто упускают ее.
Цель объединения - сохранить память , используя одну и ту же область памяти для хранения разных объектов в разное время. Вот и все.
Это как комната в отеле. Разные люди живут в нем в течение непересекающихся периодов времени. Эти люди никогда не встречаются и вообще ничего не знают друг о друге. Правильно управляя распределением времени между комнатами (т.е. следя за тем, чтобы разные люди не были назначены на одну комнату одновременно), относительно небольшая гостиница может предоставить жилье относительно большому количеству людей, то есть, какие гостиницы для.
Это именно то, что делает союз. Если вы знаете, что несколько объектов в вашей программе содержат значения с неперекрывающимися значениями времени жизни, вы можете «объединить» эти объекты в объединение и тем самым сэкономить память. Точно так же, как в гостиничном номере в каждый момент времени имеется не более одного «активного» арендатора, в профсоюзе в каждый момент времени программы может быть не более одного «активного» члена. Только «активный» член может быть прочитан. Записав другого участника, вы переключаете «активный» статус на другого участника.
По какой-то причине эта первоначальная цель объединения была «переопределена» чем-то совершенно иным: написание одного члена союза, а затем проверка его через другого члена. Этот вид реинтерпретации памяти (так называемый "наказание типа")
не является допустимым использованием союзов. Обычно это приводит к неопределенному поведению, описанному как создание определяемого реализацией поведения в C89 / 90.РЕДАКТИРОВАТЬ: Использование союзов для целей типа наказания (т.е. написание одного члена, а затем чтение другого) было дано более подробное определение в одном из Технических исправлений к стандарту C99 (см. DR # 257 и DR # 283 ). Однако имейте в виду, что формально это не защищает вас от непреднамеренного поведения при попытке прочитать представление ловушки.
источник
<time.h>
как для Windows, так и для Unix. Отклонение его как «недопустимый» и «неопределенный» на самом деле не достаточно, если меня попросят понять код, который работает именно таким образом.Вы можете использовать объединения для создания структур, подобных следующему, в котором есть поле, которое сообщает нам, какой компонент объединения фактически используется:
источник
int
илиchar*
для 10 предметов объекта []; в этом случае я могу фактически объявить отдельные структуры для каждого типа данных вместо VAROBJECT? Разве это не уменьшит беспорядок и не займет меньше места?Поведение не определено с языковой точки зрения. Учтите, что разные платформы могут иметь разные ограничения в выравнивании памяти и порядке байтов. Код с прямым порядком байтов по сравнению с машиной с прямым порядком байтов будет по-разному обновлять значения в структуре. Исправление поведения в языке потребовало бы, чтобы все реализации использовали один и тот же порядок байтов (и ограничения выравнивания памяти ...), ограничивающие использование.
Если вы используете C ++ (вы используете два тега) и действительно заботитесь о переносимости, то вы можете просто использовать структуру и предоставить установщик, который принимает
uint32_t
и устанавливает поля соответствующим образом с помощью операций битовой маски. То же самое можно сделать в Си с помощью функции.Редактировать : я ожидал, что AProgrammer запишет ответ для голосования и закроет его. Как отмечалось в некоторых комментариях, порядок байтов рассматривается в других частях стандарта, позволяя каждой реализации решать, что делать, а выравнивание и заполнение также могут обрабатываться по-разному. Теперь, строгие правила псевдонимов, на которые AProgrammer неявно ссылается, являются здесь важным моментом. Компилятору разрешается делать предположения о модификации (или отсутствии модификации) переменных. В случае объединения компилятор может переупорядочить инструкции и переместить чтение каждого компонента цвета поверх записи в переменную цвета.
источник
Самое частое использование, с которым
union
я регулярно сталкиваюсь - это псевдонимы .Учтите следующее:
Что это делает? Это позволяет чистый, аккуратный доступ к
Vector3f vec;
членам с любым именем:или с помощью целочисленного доступа в массив
В некоторых случаях доступ по имени - это самое ясное, что вы можете сделать. В других случаях, особенно когда ось выбирается программно, проще всего получить доступ к оси по числовому индексу - 0 для x, 1 для y и 2 для z.
источник
type-punning
что также упоминается в вопросе. Также пример в вопросе показывает аналогичный пример.Как вы говорите, это строго неопределенное поведение, хотя оно будет «работать» на многих платформах. Настоящая причина использования союзов - это создание вариантов записей.
Конечно, вам также нужен какой-то дискриминатор, чтобы сказать, что на самом деле содержит вариант. И обратите внимание, что в C ++ объединения не очень полезны, потому что они могут содержать только POD-типы - фактически те, которые не имеют конструкторов и деструкторов.
источник
В Си это был хороший способ реализовать что-то вроде варианта.
Во времена маленькой памяти эта структура использует меньше памяти, чем структура, в которой есть все члены.
Кстати, С обеспечивает
для доступа к битовым значениям.
источник
Хотя это строго неопределенное поведение, на практике оно будет работать практически с любым компилятором. Это настолько широко используемая парадигма, что любой уважающий себя компилятор должен будет делать «правильные вещи» в таких случаях, как этот. Это, безусловно, предпочтительнее, чем наказание типов, которое может генерировать испорченный код с некоторыми компиляторами.
источник
В C ++, Boost Variant реализует безопасную версию объединения, разработанную для максимально возможного предотвращения неопределенного поведения.
Его характеристики идентичны
enum + union
конструкции (стек выделен и т. Д.), Но он использует список шаблонов типов вместоenum
:)источник
Поведение может быть неопределенным, но это просто означает, что не существует «стандарта». Все достойные компиляторы предлагают #pragmas для управления упаковкой и выравниванием, но могут иметь разные значения по умолчанию. Значения по умолчанию также будут меняться в зависимости от используемых настроек оптимизации.
Кроме того, профсоюзы не только для экономии места. Они могут помочь современным компиляторам с типом штамповки. Если вы
reinterpret_cast<>
все, компилятор не может делать предположения о том, что вы делаете. Возможно, придется выбросить то, что он знает о вашем типе, и начать заново (принудительная запись в память, что в наши дни очень неэффективно по сравнению с тактовой частотой процессора).источник
Технически это не определено, но на самом деле большинство (все?) Компиляторов обрабатывают его точно так же, как и использование
reinterpret_cast
одного типа к другому, результатом которого является реализация реализации. Я не потерял бы сон из-за вашего текущего кода.источник
Для еще одного примера фактического использования объединений платформа CORBA сериализует объекты, используя подход с теговым объединением. Все пользовательские классы являются членами одного (огромного) объединения, а целочисленный идентификатор сообщает демаршаллеру, как следует интерпретировать объединение.
источник
Другие упоминали различия в архитектуре (little - big endian).
Я прочитал проблему о том, что, поскольку память для переменных является общей, то путем записи одной переменной другие изменяются, и, в зависимости от их типа, значение может быть бессмысленным.
например. union {float f; int i; } Икс;
Запись в xi была бы бессмысленной, если бы вы потом читали из xf - если только это не то, что вы намеревались для того, чтобы взглянуть на компоненты знака, экспоненты или мантиссы поплавка.
Я думаю, что есть также проблема выравнивания: если некоторые переменные должны быть выровнены по словам, вы можете не получить ожидаемый результат.
например. union {char c [4]; int i; } Икс;
Если гипотетически на некоторой машине символ должен быть выровнен по словам, то c [0] и c [1] будут совместно использовать память с i, но не c [2] и c [3].
источник
memcpy()
от одного к другому. Некоторые системы могут спекулятивно выравниватьchar[]
распределения, которые происходят вне структур / объединений по этой и другим причинам. В существующем примере предположение о том, чтоi
все элементы будут перекрываться,c[]
непереносимо, но это потому, что на это нет гарантииsizeof(int)==4
.На языке Си, как это было задокументировано в 1974 году, все члены структуры имели общее пространство имен, и значение «ptr-> member» было определено как добавление смещения члена к «ptr» и доступ к результирующему адресу с использованием типа члена. Этот дизайн позволил использовать один и тот же ptr с именами элементов, взятыми из разных определений структуры, но с одинаковым смещением; Программисты использовали эту способность для различных целей.
Когда членам структуры были назначены их собственные пространства имен, стало невозможно объявить два члена структуры с одинаковым смещением. Добавление объединений к языку позволило достичь той же семантики, которая была доступна в более ранних версиях языка (хотя невозможность экспортировать имена в окружающий контекст, возможно, все еще требовала использования поиска / замены для замены элемента foo-> в foo-> type1.member). Важно было не столько, чтобы люди, которые добавляли союзы, имели в виду какую-то конкретную цель использования, а скорее то, что они предоставляют средство, с помощью которого программисты, которые полагались на более раннюю семантику, для какой-либо цели , все еще могли бы достичь та же семантика, даже если они должны были использовать другой синтаксис для этого.
источник
Вы можете использовать союз по двум основным причинам:
На самом деле это скорее хакерский стиль в стиле C для быстрого написания кода на основе того, что вы знаете, как работает архитектура памяти целевой системы. Как уже говорилось, вы можете сойти с рук, если на самом деле не ориентированы на множество разных платформ. Я полагаю, что некоторые компиляторы могут позволять вам также использовать директивы упаковки (я знаю, что они используют для структур)?
Хороший пример 2. можно найти в типе VARIANT, широко используемом в COM.
источник
Как уже упоминалось, объединения, объединенные с перечислениями и заключенные в структуры, могут использоваться для реализации теговых объединений. Одним из практических применений является реализация Rust
Result<T, E>
, которая изначально реализована с использованием чистогоenum
(Rust может содержать дополнительные данные в вариантах перечисления). Вот пример C ++:источник