Назначение союзов в C и C++
я использовал союзы раньше удобно; сегодня я был встревожен, когда я читал этот пост и узнал, что этот код
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 вперед) это законно сделать (спасибо Паскаль Cuoq для поднятия этого). Тем не менее, попытка сделать он все еще может привести к неопределенному поведению, если считанное значение недопустимо (так называемое "представление ловушки") для типа, через который оно считывается. В противном случае значение определяется реализацией.
-
C89 / 90 вызвал это под неопределенным поведение (приложение J) и книга K&R говорят, что это определенная реализация. Цитата из K&R:
это цель объединения-одна переменная, которая может законно содержать любой из нескольких типов. [...] до тех пор, пока использование непротиворечиво: извлеченный тип должен быть самым последним сохраненным типом. Программист обязан отслеживать, какой тип в настоящее время хранится в объединении; результаты зависят от реализации, если что-то хранится как один тип и извлекается как другой.
-
извлечение из TC++PL Страуструпа (акцент мой)
использование союзы могут иметь важное значение для compatness данных [...] иногда неправильно используется для " преобразования типов".
прежде всего, этот вопрос (название которого остается неизменным с момента моего запроса) был поставлен с намерением понять цель союзов, а не на какой стандарт позволяет например. Использование наследования для повторного использования кода, конечно, разрешено стандартом C++, но это не было целью или первоначальным намерением введения наследования как функции языка C++. Именно поэтому ответ Андрея продолжает оставаться принятым.
14 ответов
цель союзов довольно очевидна, но по какой-то причине люди пропускают ее довольно часто.
цель союза -для сохранения памяти используя одну и ту же область памяти для хранения разных объектов в разное время. вот и все.
Это как номер в гостинице. Различные люди живут в нем в течение неперекрывающихся периодов времени. Эти люди никогда не встречаются и вообще ничего не знают друг о друге. Путем правильно управлять разделение времени номеров (т. е., убедившись, что разные люди не назначаются в один номер одновременно), относительно небольшой отель может предоставить размещение относительно большому количеству людей, для чего и предназначены отели.
Это именно то, что делает union. Если вы знаете, что несколько объектов в вашей программе содержат значения с неперекрывающимися значениями времени жизни, вы можете "объединить" эти объекты в объединение и таким образом сохранить память. Так же, как в гостиничном номере есть не более одного "активный" арендатор в каждый момент времени, союз имеет не более одного "активного" члена в каждый момент времени программы. Только" активный " член может быть прочитан. Записывая в другой член, вы переключаете статус "Активный" на этот другой член.
по какой-то причине эта первоначальная цель Союза была "переопределена" чем-то совершенно другим: написать одного члена Союза, а затем проверить его через другого члена. Этот вид переинтерпретации памяти (он же "тип каламбура") недопустимое использование союзов. Это обычно приводит к неопределенному поведению описывается как производящее поведение, определяемое имплементацией, в C89 / 90.
EDIT: использование союзов для целей каламбура типа (т. е. запись одного члена, а затем чтение другого) было дано более подробное определение в одном из технических исправлений к стандарту C99 (см. DR#257 и DR#283). Однако, имейте в виду, что формально это не защищает вы не сталкиваетесь с неопределенным поведением, пытаясь прочитать представление trap.
вы можете использовать объединения для создания следующих структур, которые содержат поле, которое сообщает нам, какой компонент объединения фактически используется:
struct VAROBJECT
{
enum o_t { Int, Double, String } objectType;
union
{
int intValue;
double dblValue;
char *strValue;
} value;
} object;
поведение не определено с точки зрения языка. Учтите, что разные платформы могут иметь разные ограничения в выравнивании памяти и endianness. Код в большом прямого или немного машина с прямым обновит значения в struct по-разному. Исправление поведения на языке потребует, чтобы все реализации использовали одну и ту же endianness (и ограничения выравнивания памяти...) ограничение использования.
Если вы используете C++ (вы используете два тега), и вы действительно заботьтесь о переносимости, тогда вы можете просто использовать структуру и предоставить сеттер, который принимает uint32_t
и устанавливает поля соответствующим образом через операции битовой маски. То же самое можно сделать в C с функцией.
редактировать: Я ожидал AProgrammer записать ответ на голосование и закрыть эту. Как указывалось в некоторых комментариях, endianness рассматривается в других частях стандарта, позволяя каждой реализации решать, что делать, а выравнивание и заполнение могут также обрабатывайте по-разному. Теперь, строго нарушает правила, что AProgrammer косвенно относится к это очень важный момент. Компилятору разрешено делать предположения о модификации (или отсутствии модификации) переменных. В случае объединения компилятор может переупорядочить инструкции и переместить чтение каждого цветового компонента поверх записи в переменную цвета.
самый общие использование union
Я регулярно сталкиваюсь это псевдоним.
рассмотрим следующее:
union Vector3f
{
struct{ float x,y,z ; } ;
float elts[3];
}
что это значит? Это позволяет чистый, аккуратный доступ на или название:
vec.x=vec.y=vec.z=1.f ;
или целое число в массиве
for( int i = 0 ; i < 3 ; i++ )
vec.elts[i]=1.f;
в некоторых случаях доступ по имени-это самая ясная вещь, которую вы можете сделать. В других случаях, особенно когда выбрана ось программно проще всего получить доступ к оси с помощью числового индекса-0 для x, 1 для y и 2 для z.
Как вы говорите, это строго неопределенное поведение, хотя он будет "работать" на многих платформах. Реальная причина использования Союзов-создание записей вариантов.
union A {
int i;
double d;
};
A a[10]; // records in "a" can be either ints or doubles
a[0].i = 42;
a[1].d = 1.23;
конечно, вам также нужен какой-то дискриминатор, чтобы сказать, что на самом деле содержит вариант. И обратите внимание, что в C++ союзы не очень полезны, потому что они могут содержать только типы POD - эффективно те, которые без конструкторов и деструкторов.
В C это был хороший способ реализовать что-то вроде варианта.
enum possibleTypes{
eInt,
eDouble,
eChar
}
struct Value{
union Value {
int iVal_;
double dval;
char cVal;
} value_;
possibleTypes discriminator_;
}
switch(val.discriminator_)
{
case eInt: val.value_.iVal_; break;
во времена памяти litlle эта структура использует меньше памяти, чем структура, которая имеет весь член.
кстати C предоставляет
typedef struct {
unsigned int mantissa_low:32; //mantissa
unsigned int mantissa_high:20;
unsigned int exponent:11; //exponent
unsigned int sign:1;
} realVal;
для доступа к битовым значениям.
В C++, Ускорение Вариант реализовать безопасную версию Союза, предназначенную для предотвращения неопределенного поведения как можно больше.
свои представления идентичны к enum + union
построить (стек тоже выделен и т. д.), Но он использует список шаблонов типов вместо enum
:)
хотя это строго неопределенное поведение, на практике оно будет работать практически с любым компилятором. Это такая широко используемая парадигма, что любой уважающий себя компилятор должен будет сделать "правильную вещь" в таких случаях. Это, безусловно, предпочтительнее, чем каламбур, который может генерировать сломанный код с некоторыми компиляторами.
технически это неопределенно, но на самом деле большинство (все?) компиляторы рассматривают его точно так же, как использование reinterpret_cast
от одного типа к другому, результатом которого является реализация. Я бы не потерял сон из-за твоего текущего кода.
для еще одного примера фактического использования объединений платформа CORBA сериализует объекты, используя подход объединения с тегами. Все пользовательские классы являются членами одного (огромного) объединения и целое число, идентификатор сообщает demarshaller, как интерпретировать объединение.
поведение может быть неопределенным, но это просто означает, что нет "стандарта". Все достойные компиляторы предлагают #pragmas для управления упаковкой и выравниванием, но могут иметь разные значения по умолчанию. Значения по умолчанию также будут меняться в зависимости от используемых настроек оптимизации.
кроме того, профсоюзы не просто для экономии пространства. Они могут помочь современным компиляторам с типом punning. Если ты ... --0--> все, что компилятор не может делать предположения о том, что вы делаете. Возможно, ему придется выбросить все, что он знает о вашем типе, и начать снова (заставляя запись обратно в память, что в наши дни очень неэффективно по сравнению с тактовой частотой процессора).
другие упоминали различия в архитектуре (little - big endian).
Я прочитал проблему, что, поскольку память для переменных является общей, то, записывая в одну, другие меняются и, в зависимости от их типа, значение может быть бессмысленным.
например. союз{ поплавок f; инт я; } x;
запись на x.я был бы бессмысленным, если бы вы тогда читали из X. F-если это не то, что вы намеревались, чтобы посмотреть на знак, экспонента или компоненты мантиссы поплавка.
Я думаю, что есть также проблема выравнивания: если некоторые переменные должны быть выровнены слово, то вы можете не получить ожидаемый результат.
например. союз{ char c[4]; инт я; } x;
Если, гипотетически, на какой-то машине символ должен быть выровнен по слову, тогда C[0] и c[1] будут делиться хранилищем с i, но не c[2] и c[3].
на языке C, как это было задокументировано в 1974 году, все члены структуры разделяли общее пространство имен, а значение "ptr->member" было определена Как добавить перемещение члена в " ptr " и доступ к результирующему адресу с помощью тип члена. Эта конструкция позволила использовать тот же ptr с элементом имена, взятые из различных определений структуры, но с одинаковым смещением; программисты использовали эту способность для различных целей.
когда членам структуры были присвоены собственные пространства имен, это стало невозможным объявить два элемента структуры с одинаковым смещением. Добавление союзов к язык позволил достичь той же семантики, что и раньше. доступны в более ранних версиях языка (хотя невозможность иметь имена, экспортированные в заключительный контекст, возможно, все еще требовали использования найти / заменить для замены Foo - >member в foo - >type1.член.) Что было важно было не столько то, что люди, которые добавленные союзы имеют какие-либо особенности целевое использование в виду, а скорее, что они обеспечивают средства, с помощью которых программисты кто полагался на прежнюю семантику?--1-->для любых целей, все равно должны удастся достичь той же семантики, даже если они должны были использовать различные синтаксис для этого.
вы можете использовать a объединение по двум основным причинам:
- удобный способ доступа к тем же данным по-разному, как в вашем примере
- способ сэкономить место, когда есть разные члены данных, из которых только один может быть "активным"
1 на самом деле больше из взлома C-стиля для короткого написания кода на основе того, что вы знаете, как работает архитектура памяти целевой системы. Как уже было сказано, вы можете нормально уйти с этим, если на самом деле вы не нацелены на множество различных платформ. Я считаю, что некоторые компиляторы могут позволить вам также использовать директивы упаковки (я знаю, что они делают на структурах)?
хороший пример 2. можно найти в вариант тип широко используется в COM.