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

Цель Союзов в 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 ]

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

Другие упоминали различия в архитектуре (little - big endian).

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

например. объединение { плавать f; int i; } x;

Писать в x.i было бы бессмысленно, если бы вы потом читали из x.f - если только это не то, что вы намеревались посмотреть на компоненты знака, экспоненты или мантиссы поплавка.

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

например. объединение { символ с [4]; int i; } x;

Если гипотетически на некоторой машине символ должен быть выровнен по слову, то c [0] и c [1] будут совместно использовать память с i, но не c [2] и c [3].

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

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

3 голосов
/ 21 сентября 2016

В языке Си, как это было задокументировано в 1974 году, все члены структуры имели общее пространство имен, и значение "ptr-> member" было определено как добавление смещения члена к "ptr" идоступ к полученному адресу, используя тип члена.Этот дизайн позволил использовать один и тот же ptr с именами элементов, взятыми из разных определений структуры, но с одинаковым смещением;программисты использовали эту возможность для различных целей.

Когда элементам структуры были назначены их собственные пространства имен, стало невозможно объявить два элемента структуры с одинаковым смещением.Добавление объединений к языку позволило достичь той же семантики, которая была доступна в более ранних версиях языка (хотя невозможность экспортировать имена в окружающий контекст, возможно, все еще требовала использования поиска / замены для замены элемента foo->в foo-> type1.member).Важно было не столько, чтобы люди, которые добавляли союзы, имели в виду какую-то конкретную цель использования, а скорее то, что они предоставляют средство, с помощью которого программисты, которые полагались на более раннюю семантику, для любой цели , должныпо-прежнему сможет достичь той же семантики, даже если для этого им пришлось использовать другой синтаксис.

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

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

  1. Удобный способ получить доступ к одним и тем же данным разными способами, как в вашем примере
  2. Способ экономии места при наличии разных элементов данных, из которых только один может быть «активным»

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

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

0 голосов
/ 27 мая 2019

Как уже упоминалось, союзы, объединенные с перечислениями и заключенные в структуры, могут использоваться для реализации помеченных союзов. Одним из практических применений является реализация Rust Result<T, E>, который изначально реализован с использованием чистого enum (Rust может содержать дополнительные данные в вариантах перечисления). Вот пример C ++:

template <typename T, typename E> struct Result {
    public:
    enum class Success : uint8_t { Ok, Err };
    Result(T val) {
        m_success = Success::Ok;
        m_value.ok = val;
    }
    Result(E val) {
        m_success = Success::Err;
        m_value.err = val;
    }
    inline bool operator==(const Result& other) {
        return other.m_success == this->m_success;
    }
    inline bool operator!=(const Result& other) {
        return other.m_success != this->m_success;
    }
    inline T expect(const char* errorMsg) {
        if (m_success == Success::Err) throw errorMsg;
        else return m_value.ok;
    }
    inline bool is_ok() {
        return m_success == Success::Ok;
    }
    inline bool is_err() {
        return m_success == Success::Err;
    }
    inline const T* ok() {
        if (is_ok()) return m_value.ok;
        else return nullptr;
    }
    inline const T* err() {
        if (is_err()) return m_value.err;
        else return nullptr;
    }

    // Other methods from https://doc.rust-lang.org/std/result/enum.Result.html

    private:
    Success m_success;
    union _val_t { T ok; E err; } m_value;
}
...