Инициализировать объединение с вложенными структурами - PullRequest
2 голосов
/ 06 ноября 2019

Я портирую код C99 на C ++ (14 или 17), и во многих местах используется инициализатор списка. Теперь я получаю ошибки компиляции и хотел бы знать самый простой способ инициализации объединения, вложенного структурой. Например, следующий фрагмент кода в C работает просто отлично:

#include <stdint.h>

typedef union Word_t
{
    uint32_t word32Bits;
    struct
    {
        uint16_t leastSignificant16Bits;
        uint16_t mostSignificant16Bits;
    };
} Word_t;

int main()
{
    Word_t w1 = (Word_t) {.word32Bits = 0x1234ABCD};
    printf("%x\n", w1.word32Bits);

    Word_t w2 = (Word_t) {.mostSignificant16Bits = 0x1234, .leastSignificant16Bits = 0xABCD};
    printf("%x\n", w2.word32Bits);

    return 0;
}


$ gcc test.c --std=c99 -o a && ./a
1234abcd
1234abcd

Однако в C ++ он не компилируется:

#include <stdint.h>

typedef union Word_t
{
    uint32_t word32Bits;
    struct
    {
        uint16_t leastSignificant16Bits;
        uint16_t mostSignificant16Bits;
    } _word16Bits;
} Word_t;

int main()
{
    Word_t w1 = (Word_t) {.word32Bits = 0x1234ABCD};
    printf("%x\n", w1.word32Bits);

    Word_t w2 = (Word_t) {.mostSignificant16Bits = 0x1234, .leastSignificant16Bits = 0xABCD};
    printf("%x\n", w2.word32Bits);

    return 0;
}


```bash
$ g++ test.c --std=c++14 -o a && ./a
test.c: In function ‘int main()’:
test.c:57:92: error: ‘Word_t’ has no non-static data member named ‘mostSignificant16Bits’
     Word_t w2 = (Word_t) {.mostSignificant16Bits = 0x1234, .leastSignificant16Bits = 0xABCD};

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


int main()
{
    Word_t w1 = (Word_t) {.word32Bits = 0x1234ABCD};
    printf("%x\n", w1.word32Bits);

    <del>Word_t w2 = (Word_t) {.mostSignificant16Bits = 0x1234, .leastSignificant16Bits = 0xABCD};
</del>

    Word_t w2 = {0};
    w2._word16Bits = {0x1234, 0xABCD};

    return 0;
}

Что работает, но не позволяет мне явно сказать, например, .mostSignificant16Bits = 0x1234 - что я считаю добрымполезного, особенно при чтении кода.

Я попробовал несколько вещей, таких как определение статических членов, создание пользовательского конструктора, но все еще не представляю, как упростить рефакторинг, который я собираюсь сделать. В идеале я хотел бы оставить объявление переменной таким, как оно есть Word_t w2 = (Word_t) {.mostSignificant16Bits = 0x1234, .leastSignificant16Bits = 0xABCD}, в то время как все изменения сделаны в определении Word_t.

Ответы [ 2 ]

2 голосов
/ 06 ноября 2019

Синтаксические проблемы

Назначенные инициализаторы в совокупной инициализации формально являются частью C ++ 20 стандарта .

Однако они имеют серьезные ограничения по сравнению с C99:

  • Они должны появляться в том же порядке, что и декларация;
  • Все обозначенные элементы должны быть прямымичлены совокупности;
  • Вложение возможно, только если вложенный инициализатор

В вашем случае скомпилируется следующее, но это не даст ожидаемых преимуществ от гибкости, которые вы ожидаете отявное имя:

Word_t w2 = (Word_t) {._word16Bits  { .leastSignificant16Bits = 0xABCD, .mostSignificant16Bits = 0x1234} };

Более серьезные проблемы

Во-первых, этот код, если он будет работать, не является переносимым: он предполагает немного порядка байтов целевой архитектуры,

Во-вторых, и это очень важно, C ++ имеет строгие ограничения на профсоюзы. Это необходимо с точки зрения согласованности жизненного цикла объекта. В частности:

[class.union] / 1 : В объединении в любой момент может быть активен не более одного не статического члена данных, то естьзначение не более одного из нестатических элементов данных может быть сохранено в объединении в любое время.

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

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

Стандарт также дает подсказки о способе изменения активного члена объединения:

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

Это, как говорится, для простых скалярных типов, может компилироваться и работать, как вы ожидаете, на большинстве основных компиляторов.

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

Почему пришло времяизменить подход?

C99 имеет меньше ограничений на союзы, как C ++. Но это также не дает твердой гарантии относительно значения, которое может быть прочитано в одном члене объединения, когда был задан другой член объединения:

6.2.6.1 / 7 : Когда значениехранящиеся в элементе объекта типа объединения, байты представления объекта, которые не соответствуют этому элементу, но соответствуют другим элементам, принимают неопределенные значения.

Это упоминается в приложении C99 /J что такое использование союзов - неопределенное поведение, которое может создать проблемы переносимости.

0 голосов
/ 06 ноября 2019

Это работает в C и C ++:

#include <stdio.h>
#include <stdint.h>

typedef union Word_t
{
    uint32_t word32Bits;
    struct
    {
        uint16_t leastSignificant16Bits;
        uint16_t mostSignificant16Bits;
    } _word16Bits;
} Word_t;

int main()
{
    Word_t w1 = {.word32Bits = 0x1234ABCD};
    printf("%x\n", w1.word32Bits);

    Word_t w2 = {._word16Bits={.leastSignificant16Bits = 0xABCD, .mostSignificant16Bits = 0x1234}};
    printf("%x\n", w2.word32Bits);

    return 0;
}

Редактировать: --std=c++2a вместо --std=c++14 необходимо для обозначенных инициализаторов.

$ g++ -Wall -Wextra test.c --std=c++2a -pedantic -pedantic-errors -o a && ./a
1234abcd
1234abcd
$ gcc -Wall -Wextra test.c -pedantic -pedantic-errors -o a && ./a
1234abcd
1234abcd

Примечание: В C ++ у вас естьуказывать инициализированные теги в том же порядке, что и в объявлении структуры.

Как уже упоминалось в комментарии Теда Лингмо, запись в один член объединения и чтение из другого является неопределенным поведением в C ++.

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

...