Профсоюзы ANSI C - действительно ли они полезны? - PullRequest
6 голосов
/ 12 августа 2010

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

Итак, понимая, что это предположение небезопасно,Интересно - кроме сохранения памяти (дух ...), что реальное использование для союзов?

Примечание: то есть, в соответствии со стандартом С. Понятно, что для конкретной реализации правила известны заранее иможно использовать в своих интересах.

РЕДАКТИРОВАТЬ: слово "небезопасно", из-за ассоциации последних лет, вероятно, является неправильным выбором формулировки, но я думаю, что намерение в ясной форме.2: Поскольку этот пункт повторяется в ответах - экономия памяти является допустимым аргументом.Я хотел знать, было ли что-то помимо этого.

Ответы [ 10 ]

10 голосов
/ 12 августа 2010

Да.

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

Там есть , однако, одна из тех функций, которые вы используете только тогда, когда они вам нужны и нуждаются вредко.

4 голосов
/ 12 августа 2010

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

Взаимная исключительность членов была бы менее очевидной, если бы вместо этого вы сделали всех union членов отдельными членами struct. Кроме того, у вас по-прежнему будет та же проблема некорректного поведения, если вы прочитаете элемент, для которого ранее не было написано, но теперь вам нужно учитывать и семантику приложения (было ли инициализировано все неиспользуемые элементы равным 0? это оставляет их как мусор?), так в этом смысле, почему не вы используете объединение?

3 голосов
/ 12 августа 2010

Вот одно законное портативное использование союзов:

struct arg {
    enum type t;
    union {
        intmax_t i;
        uintmax_t u;
        long double f;
        void *p;
        void (*fp)(void);
    } v;
};

В сочетании с информацией о типе в t, struct arg может содержать любое числовое значение или значение указателя. Вся структура, вероятно, будет иметь размер 16-32 байта по сравнению с 40-80 байтами, если объединение не использовалось. Разница была бы еще более экстремальной, если бы я хотел хранить каждый возможный исходный числовой тип отдельно (знаковый символ, короткий, int, длинный, длинный длинный, беззнаковый символ, беззнаковый короткий, ...), а не преобразовывать их в наибольшее знаковое число / unsigned / тип с плавающей точкой перед их сохранением.

Кроме того, хотя и не «переносимо» предполагать что-либо о представлении типов, отличных от unsigned char, по стандарту разрешено использовать объединение с unsigned char или приводить указатель к unsigned char * и осуществлять доступ произвольный объект данных таким образом. Если вы записываете эту информацию на диск, она не будет переносимой на другие системы, которые использовали другие представления, но она все еще может быть полезна во время выполнения - например, реализация хеш-таблицы для хранения значений double. (Кто-нибудь хочет исправить меня, если проблемы с битами заполнения делают эту технику недействительной?) Если ничего другого, ее можно использовать для реализации memcpy (не очень полезно, поскольку стандартная библиотека предоставляет вам гораздо лучшую реализацию) или (что более интересно) memswap функция, которая может поменять два объекта произвольного размера с ограниченным временным пространством. Это теперь немного перешло в область использования союзов и теперь распространяется на территорию unsigned char *, но это тесно связано.

3 голосов
/ 12 августа 2010

Даже если не учитывать конкретную реализацию, где выравнивание и упаковка известны, союзы все еще могут быть полезны.

Они позволяют вам хранить одно из множества значений в одном блоке памяти, например:

typedef struct {
    int type;
    union {
        type1 one;
        type2 two;
    }
} unioned_type;

И да, является непереносимым, чтобы ожидать сохранения ваших данных в one и чтения их из two. Но если вы просто используете type, чтобы указать, что является базовой переменной, вы можете легко получить ее без приведения.

Другими словами:

unioned_type ut;
ut.type = 1;
ut.one = myOne;
// Don't use ut.two here unless you know the underlying details.

хорошо, если вы используете type, чтобы решить, что переменная type1 хранится там.

3 голосов
/ 12 августа 2010

Вопрос содержит ограничение, которое может запретить действительный ответ ...

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

Другими словами: профсоюзы не имеютчтобы быть полезным для стандартного определенного поведения чтобы быть полезным в целом, они могли просто позволить кому-то использовать причуды своей целевой машины, не прибегая к сборке.

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

Я надеюсь, что это имеет смысл.

3 голосов
/ 12 августа 2010

Да, профсоюзы могут быть непереносимыми и небезопасными, но имеют свое применение. Например, он может ускорить процесс, избавляя от необходимости приводить uint32 к char [4]. Это может пригодиться, если вы пытаетесь выполнить маршрутизацию по IP-адресу в SW, но тогда ваш процессор должен иметь порядок сети Думайте о союзах как об альтернативе кастинга, с меньшим количеством машинных инструкций. Кастинг имеет аналогичные недостатки.

2 голосов
/ 12 августа 2010

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

OTOH, синтаксический анализатор, для одного примера, обычно имеет объединение для представления значенийв выражениях.[Редактировать: я заменяю пример парсера на один, надеюсь, он немного более понятен]:

Давайте рассмотрим файл ресурсов Windows.Вы можете использовать его для определения ресурсов, таких как меню, диалоговые окна, значки и т. Д. Примерно так:

#define mn1 2

mn1 MENU
{
    MENUITEM "File", -1, MENUBREAK
}

ico1 "junk.ico"

dlg1 DIALOG 100, 0, 0, 100, 100 
BEGIN
    FONT 14, "Times New Roman"
    CAPTION "Test Dialog Box"
    ICON ico1, 700, 20, 20, 20, 20
    TEXT "This is a string", 100, 0, 0, 100, 10
    LTEXT "This is another string", 200, 0, 10, 100, 10
    RTEXT "Yet a third string", 300, 0, 20, 100, 10
    LISTBOX 400, 20, 20, 100, 100
    CHECKBOX "A combobox", 500, 100, 100, 200, 10
    COMBOBOX 600, 100, 210, 200, 100
    DEFPUSHBUTTON "OK", 75, 200, 200, 50, 15
END

При разборе MENU отображается определение меню;анализ DIALOG дает определение диалога и так далее.В синтаксическом анализаторе мы представляем это как объединение:

%union { 
        struct control_def {
                char window_text[256];
                int id;
                char *class;
                int x, y, width, height;
                int ctrl_style;
        } ctrl;

        struct menu_item_def { 
                char text[256];
                int identifier;
        } item;

        struct menu_def { 
                int identiifer;
                struct menu_item_def items[256];
        } mnu;

        struct font_def { 
                int size;
                char filename[256];
        } font;

        struct dialog_def { 
                char caption[256];
                int id;
                int x, y, width, height;
                int style;
                struct menu_def *mnu;
                struct control_def ctrls[256];
                struct font_def font;
        } dlg;

        int value;
        char text[256];
};

Затем мы указываем тип, который будет создан путем анализа определенного типа выражения.Например, определение шрифта в файле становится font членом объединения:

%type <font> font

Просто для пояснения, часть <font> ссылается на созданный член объединения и второй «шрифт»ссылается на правило синтаксического анализатора, которое даст результат такого типа.Вот правило для этого конкретного случая:

font: T_FONT T_NUMBER "," T_STRING { 
    $$.size = $2; 
    strcpy($$.filename,$4); 
};

Да, теоретически мы могли бы использовать структуру вместо объединения здесь - но, кроме потери памяти, она просто не делаетсмысл.Определение шрифта в файле only определяет шрифт.Не имеет смысла создавать структуру, которая включает определение меню, определение значка, номер, строку и т. Д. В дополнение к определенному шрифту.[конец редактирования]

Конечно, использование союзов для экономии памяти редко бывает очень важным.Хотя сейчас это может показаться довольно тривиальным, но когда было много 64 КБ ОЗУ, экономия памяти означала гораздо больше.

2 голосов
/ 12 августа 2010

Один из способов использовать союзы, с которыми я сталкивался, - скрывать данные.

Скажем, у вас есть структура, которая является буфером

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

РЕДАКТИРОВАТЬ: вот пример

struct X
{
  int a;
};

struct Y
{
  int b;
};

union Public
{
   struct X x;
   struct Y y;
};

здесь тот, кто использует объединение XY, может привести XY к структуре X или Y

с учетом функции:

void foo(Public* arg)
{   
...

вы можете получить доступ как к struct X, так и к struct Y

но тогда вы хотите ограничить доступ, чтобы пользователь не знал о X

имя объединения остается прежним, но часть struct X недоступна (через заголовок)

void foo(Public* arg)
{
   // Public is still available but struct X is gone, 
   // user can only cast to struct Y

   struct Y* p = (struct Y*)arg;
...
0 голосов
/ 26 августа 2018

Вот практический пример:

Существуют микроконтроллеры, которые в своей энергонезависимой памяти хранят данные в байтовых блоках.Как вы могли бы легко хранить множество поплавков в этих воспоминаниях?Мы знаем, что числа с плавающей запятой в C имеют длину 32 бита (4 байта), поэтому:

union float_uint8
{
    uint8 i[KNFLOATS*4]; //or KNFLOATS*sizeof(float)
    float f[KNFLOATS];
};

Теперь вы можете хранить / обращаться к плавающим переменным с помощью переменных / указателей типа float_uint8, а с помощью цикла вы можете легко хранить их в памяти какразложенные байты без какого-либо преобразования или декомпозиции.И та же история повторяется при чтении памяти.Даже вам не нужно знать, как распределяются числа в байтах для хранения или восстановления данных, хранящихся в памяти.

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

0 голосов
/ 13 августа 2010

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

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

Например: Рассмотрим тип данных объединения следующим образом:

/* Data1 Bit Defintion */
typedef union 
{
    struct STRUCT_REG_DATA
    {
        unsigned int u32_BitField1  : 3;
        unsigned int u32_BitField2  : 2;
        unsigned int u32_BitField3  : 1;
        unsigned int u32_BitField4  : 2;                
    } st_RegData;

    unsigned int u32_RegData;

} UNION_REG_DATA;

Для изменения всего содержимого регистра,

UNION_REG_DATA  un_RegData;
un_RegData. u32_RegData = 0x77;

Чтобы изменить содержимое однобитового поля (для Ex Bitfield3)

un_RegData.st_RegData.u32_BitField3 = 1;

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

...