Профсоюзы ANSI C - они действительно полезны?

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

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

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

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

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

6
задан timrau 15 August 2012 в 17:07
поделиться

9 ответов

Да.

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

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

10
ответ дан 8 December 2019 в 03:08
поделиться

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

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

4
ответ дан 8 December 2019 в 03:08
поделиться

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

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

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

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

Надеюсь, это имеет смысл.

3
ответ дан 8 December 2019 в 03:08
поделиться

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

3
ответ дан 8 December 2019 в 03:08
поделиться

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

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

typedef struct {
    int type;
    union {
        type1 one;
        type2 two;
    }
} unioned_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
ответ дан 8 December 2019 в 03:08
поделиться

Вот одно допустимое переносимое использование объединений:

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 байтами, если бы объединение не использовалось. Разница была бы еще более значительной, если бы я хотел сохранить каждый возможный исходный числовой тип отдельно (signed char, short, int, long, long long, unsigned char, unsigned short, ...), а не преобразовывать их до наибольшего подписанного / беззнаковый / тип с плавающей запятой перед их сохранением.

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

3
ответ дан 8 December 2019 в 03:08
поделиться

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

Допустим, у вас есть структура, которая является буфером

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

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

struct X
{
  int a;
};

struct Y
{
  int b;
};

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

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

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

void foo(Public* arg)
{   
...

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

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

, имя объединения остается тем же, но часть структуры 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;
...
2
ответ дан 8 December 2019 в 03:08
поделиться

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

Тем не менее, парсер, например, обычно использует объединение для представления значений в выражениях. [Edit: I'm replacing the parser example with one I hope is a bit more understandable]:

Рассмотрим файл ресурсов 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); 
};

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

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

2
ответ дан 8 December 2019 в 03:08
поделиться

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

Используя тип данных 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;

Для изменения содержимого одного битового поля (например, Bitfield3)

un_RegData.st_RegData.u32_BitField3 = 1;

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

0
ответ дан 8 December 2019 в 03:08
поделиться
Другие вопросы по тегам:

Похожие вопросы: