Назначение союзов в 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. удобный способ доступа к тем же данным по-разному, как в вашем примере
  2. способ сэкономить место, когда есть разные члены данных, из которых только один может быть "активным"

1 на самом деле больше из взлома C-стиля для короткого написания кода на основе того, что вы знаете, как работает архитектура памяти целевой системы. Как уже было сказано, вы можете нормально уйти с этим, если на самом деле вы не нацелены на множество различных платформ. Я считаю, что некоторые компиляторы могут позволить вам также использовать директивы упаковки (я знаю, что они делают на структурах)?

хороший пример 2. можно найти в вариант тип широко используется в COM.