Цель Союзов в C и C ++ - PullRequest
       141

Цель Союзов в C и C ++

220 голосов
/ 22 февраля 2010

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

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 и далее) это допустимо ( благодаря Pascal Cuoq за это). Однако попытка сделать может привести к неопределенному поведению , если прочитанное значение окажется недопустимым (так называемое «представление ловушки») для типа, через которое оно читается. В противном случае считываемое значение определяется реализацией.
  • C89 / 90 вызвал это при неопределенном поведении (Приложение J), а в книге K & R говорится, что его реализация определена. Цитата из K & R:

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

  • Извлечение из ТС ++ PL Страуструпа (ударная мина)

    Использование союзов может иметь важное значение для обеспечения совместимости данных [...] иногда неправильно используется для "преобразования типов ".

Прежде всего, этот вопрос (название которого остается неизменным со времени моего запроса) был задан с намерением понять цель профсоюзов, а не то, что позволяет стандарт Например. Использование наследования для повторного использования кода, конечно, разрешено стандартом C ++, но не было целью или первоначальным намерением ввести наследование как функцию языка C ++ . По этой причине ответ Андрея продолжает оставаться принятым.

Ответы [ 15 ]

348 голосов
/ 22 февраля 2010

Цель профсоюзов довольно очевидна, но по некоторым причинам люди часто упускают ее.

Цель объединения - для экономии памяти с использованием одной и той же области памяти для хранения разных объектов в разное время. Вот и все.

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

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

По какой-то причине эта первоначальная цель объединения была "переопределена" чем-то совершенно другим: написание одного члена союза и затем проверка его через другого члена. Этот вид реинтерпретации памяти (также известный как «наказание по типу») является недопустимым использованием союзов. Обычно это приводит к неопределенному поведению описывается как создание поведения, определенного реализацией в C89 / 90.

РЕДАКТИРОВАТЬ: Использование союзов для целей типа наказания (т.е. написание одного члена, а затем чтение другого) было дано более подробное определение в одном из Технических исправлений к стандарту C99 (см. DR # 257 и DR # 283 ). Однако имейте в виду, что формально это не защищает вас от непреднамеренного поведения при попытке прочитать представление ловушки.

36 голосов
/ 22 февраля 2010

Вы можете использовать союзы для создания структур, подобных следующему, который содержит поле, которое сообщает нам, какой компонент объединения фактически используется:

struct VAROBJECT
{
    enum o_t { Int, Double, String } objectType;

    union
    {
        int intValue;
        double dblValue;
        char *strValue;
    } value;
} object;
34 голосов
/ 22 февраля 2010

Поведение не определено с языковой точки зрения. Учтите, что разные платформы могут иметь разные ограничения в выравнивании памяти и порядке байтов. Код с прямым порядком байтов и с прямым порядком байтов будет по-разному обновлять значения в структуре. Исправление поведения в языке потребовало бы, чтобы все реализации использовали один и тот же порядок байтов (и ограничения выравнивания памяти ...), ограничивающий использование.

Если вы используете C ++ (вы используете два тега) и действительно заботитесь о переносимости, тогда вы можете просто использовать структуру и предоставить установщик, который принимает uint32_t и устанавливает поля соответствующим образом с помощью операций битовой маски. То же самое можно сделать в Си с помощью функции.

Редактировать : Я ожидал, что AProgrammer запишет ответ для голосования и закроет его. Как отмечалось в некоторых комментариях, порядок байтов рассматривается в других частях стандарта, позволяя каждой реализации решать, что делать, и выравнивание и заполнение также могут обрабатываться по-разному. Теперь, строгие правила псевдонимов, на которые AProgrammer неявно ссылается, являются здесь важным моментом. Компилятору разрешается делать предположения о модификации (или отсутствии модификации) переменных. В случае объединения компилятор может переупорядочить инструкции и переместить чтение каждого компонента цвета поверх записи в переменную цвета.

17 голосов
/ 12 августа 2013

Наиболее распространенное использование union Я регулярно сталкиваюсь с алиасами .

Рассмотрим следующее:

union Vector3f
{
  struct{ float x,y,z ; } ;
  float elts[3];
}

Чтоэто делает?Он обеспечивает чистый, аккуратный доступ к элементам Vector3f vec; по либо имени:

vec.x=vec.y=vec.z=1.f ;

или целочисленному доступу в массив

for( int i = 0 ; i < 3 ; i++ )
  vec.elts[i]=1.f;

Inв некоторых случаях доступ по имени - самое ясное, что вы можете сделать.В других случаях, особенно когда ось выбирается программно, проще всего получить доступ к оси по числовому индексу - 0 для x, 1 для y и 2 для z.

9 голосов
/ 22 февраля 2010

Как вы говорите, это строго неопределенное поведение, хотя оно будет "работать" на многих платформах. Истинная причина использования союзов - создание вариантов записей.

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 - фактически те, которые не имеют конструкторов и деструкторов.

7 голосов
/ 22 февраля 2010

В 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;

Во времена маленькой памяти эта структура использует меньше памяти, чем структура, в которой есть все члены.

Кстати, С обеспечивает

    typedef struct {
      unsigned int mantissa_low:32;      //mantissa
      unsigned int mantissa_high:20;
      unsigned int exponent:11;         //exponent
      unsigned int sign:1;
    } realVal;

для доступа к битовым значениям.

5 голосов
/ 18 января 2012

Поведение может быть неопределенным, но это просто означает, что не существует «стандарта». Все достойные компиляторы предлагают # pragmas для управления упаковкой и выравниванием, но могут иметь разные значения по умолчанию. Значения по умолчанию также будут меняться в зависимости от используемых настроек оптимизации.

Кроме того, союзы не просто для экономии места. Они могут помочь современным компиляторам с типом штамповки. Если вы reinterpret_cast<> все, что компилятор не может делать предположения о том, что вы делаете. Возможно, придется выбросить все, что он знает о вашем типе, и начать заново (принудительная запись в память, что в наши дни очень неэффективно по сравнению с тактовой частотой процессора).

5 голосов
/ 22 февраля 2010

В C ++ Boost Variant реализует безопасную версию объединения, разработанную для максимально возможного предотвращения неопределенного поведения.

Его характеристики идентичны конструкции enum + union (стек выделен и т. Д.), Но вместо enum:)

используется список шаблонов типов.
5 голосов
/ 22 февраля 2010

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

4 голосов
/ 22 февраля 2010

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

...