Доступ к членам союза C++ и неопределенное поведение

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

typedef struct PacketHeader {
    //Byte 0
    uint8_t  bRes                           :4;
    uint8_t  bEmpty                         :1;
    uint8_t  bWait                          :1;
    uint8_t  bErr                           :1;
    uint8_t  bEnable                        :1;
    //Byte 1
    uint8_t  bInst                          :4;
    uint8_t  bCount                         :3;
    uint8_t  bRres                          :1;
    //Bytes 2, 3
    union {
        uint16_t wId;    /* Needed for Endian swapping */
        struct{
            uint16_t wMake                  :4;
            uint16_t wMod                   :12;
        };
    };
} PacketHeader;

В зависимости от того, как используются экземпляры структуры, требуемый порядок байтов структура может быть с прямым или прямым порядком байтов. Поскольку первые два байта структура представляет собой каждый отдельный байт, их не нужно изменять, когда порядок байтов изменения. Байты 2 и 3, хранящиеся как одиночные uint16_t, — единственные байты, которые нам нужны. swap, чтобы добиться желаемого порядка байтов. Чтобы добиться замены порядка байтов, мы имеем выполнял следующее:

//Returns a constructed instance of PacketHeader with relevant fields set and the provided counter value
PacketHeader myHeader = mmt::BuildPacketHeader(count);

uint16_t packetIdFlipped;
//Swap positions of byte 2 and 3
packetIdFlipped = myHeader.wId << 8;
packetIdFlipped |= (uint16_t)myHeader.wId >> 8;

myHeader.wId = packetIdFlipped;

Функция BuildPacketHeader(uint8_t) присваивает значения членам wMake и wMod явно и не пишет участнику wId. Мой вопрос касается безопасность чтения члена wId внутри возвращенного экземпляра состав.

Такие вопросы, как Доступ к неактивному члену союза и неопределенное поведение?, Назначение союзов в C и C++, и Раздел 10.4 проекта стандарта, который у меня есть, упоминает неопределенное поведение, возникающее при доступе к неактивному члену объединения в C++.

Параграф 1 раздела 10.4 связанного проекта также содержит следующее примечание, хотя я не уверен, что понимаю всю используемую терминологию:

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

Чтение myHeader.wId в строке packetIdFlipped = myHeader.wId << 8 неопределенное поведение?

Является ли безымянная структура активным членом, поскольку она была последним членом, записанным при вызове функции?

Или это примечание означает, что доступ к члену wId безопасен, поскольку он и структура имеют общий тип? (и это и есть то, что подразумевается под общей начальной последовательностью?)

заранее спасибо

Анонимные структуры плохо сформированы в C++. т.е. это: struct{ /* members */ };

eerorika 21.03.2019 14:58

@eerorika Спасибо - я даже не понял этого. Изучив его, я получил комментарий Подобно объединению, безымянный член структуры, тип которой является структурой без имени, называется анонимной структурой. Каждый член анонимной структуры считается членом объемлющей структуры или объединения. Это применяется рекурсивно, если окружающая структура или объединение также являются анонимными.здесь. Означает ли это, что приведенный выше код на самом деле не является UB, поскольку каждый член внутренней структуры и объединения считается членами struct PacketHeader?

cprlkleg 21.03.2019 15:48

Это УБ. Анонимность союза ничего не меняет.

eerorika 21.03.2019 15:53

Кажется, я не нашел кнопку редактирования комментария, но при более внимательном чтении я думаю, что эта ссылка все равно относилась к C. Большое спасибо, еще раз

cprlkleg 21.03.2019 15:54
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
5
4
1 228
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Is reading myHeader.wId in the line packetIdFlipped = myHeader.wId << 8 undefined behaviour?

да. Вы назначили wMake и wMod, сделав безымянную структуру активным элементом, поэтому wId является неактивным элементом, и вам не разрешено читать из него, не установив для него значение.

and is this what is meant by common initial sequence?

общая начальная последовательность — это когда два стандартные типы макетов совместно используют одни и те же элементы в одном и том же порядке. В

struct foo
{
    int a;
    int b;
};

struct bar
{
    int a;
    int b;
    int c;
};

a и b имеют один и тот же тип в foo и bar, поэтому они являются их общей начальной последовательностью. Если вы поместите объекты foo и bar в союз, будет безопасно читать a или b из объекта with после того, как он установлен в одном из них.

Это не ваш случай, поскольку wId не является стандартной структурой макета.

Достаточно ли обернуть wId в struct ID{ ... }, чтобы получить стандартный тип макета? И, если да, будет ли он разделять общая начальная последовательность со следующей анонимной (в приведенном примере — будет изменена теперь, когда eeorika указала, что она плохо определена) структурой, поскольку каждая из них состоит из uint16_t начального члена? Или тот факт, что элемент последней структуры является битовым полем, означает, что они не являются общими типами?

cprlkleg 21.03.2019 18:06

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

cprlkleg 21.03.2019 18:13

@cprlkleg Это все равно было бы незаконно. Как вы выяснили, обе структуры должны иметь битовое поле, чтобы быть законными.

NathanOliver 21.03.2019 18:21

The function BuildPacketHeader(uint8_t) assigns values to the members wMake and wMod explicitly, and does not write to the member wId. My question is regarding the safety of reading from the member wId inside the returned instance of the structure.

Да, это УБ. Это не значит, что он не работает, просто он может не работать. Чтобы избежать этого, вы можете использовать memcpy внутри BuildPacketHeader (см. это и это).

Спасибо за ответ и подсказку memcpy. Является ли это все еще UB, учитывая, что внутреннее объединение и структура являются анонимными (как указано eeorika), и следующий комментарий Подобно объединению, безымянный член структуры, тип которой является структурой без имени, называется анонимной структурой. Каждый член анонимной структуры считается членом объемлющей структуры или объединения. Это применяется рекурсивно, если окружающая структура или объединение также являются анонимными., источник EDIT: ссылка, похоже, относится к C в любом случае, мои извинения

cprlkleg 21.03.2019 15:53

То, что говорит стандартный вид С++, - это две структуры A и B и следующее объединение:

union U
{
  A a;
  B b;
};

Ниже приведен допустимый код:

U u;

A a;
u.a = a;
a = u.a;

B b;
u.b = b;
b = u.b;

Вы читаете и пишете однотипно. Это, очевидно, правильный код.

Но проблема возникает, когда у вас есть следующий код:

A a;
B b;
u.a = a;
b = u.b;

Что мы знаем об А и Б? Сначала в объединении они делят одно и то же пространство памяти. Теперь стандарт C++ прямо объявил это поведение неопределенным.

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

Наконец, с прагматической точки зрения, если вы не возитесь с отступами и получите стандартный макет, компилятор, как правило, поступит правильно, поскольку это довольно старый шаблон использования в C, и его нарушение приведет к поломке МНОГО кода.

Другие вопросы по теме