Доступ члена C ++ и неопределенное поведение - PullRequest
3 голосов
/ 21 марта 2019

В настоящее время я работаю над проектом, в котором мне предоставлено следующее structre. Моя работа на 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, являются единственными байтами, которые нам нужны поменяйте местами, чтобы получить желаемый порядок байтов. Чтобы получить обмен по порядку байтов, у нас есть выполнял следующее:

//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 безопасен, так как он и структура имеют общий тип? (и означает ли это общая начальная последовательность ?)

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

Ответы [ 3 ]

4 голосов
/ 21 марта 2019

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

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

1 голос
/ 21 марта 2019

Что говорит стандартный вид C ++, дано две структуры 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 ++ явно объявил его как неопределенное поведение.

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

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

1 голос
/ 21 марта 2019

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

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

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

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

struct foo
{
    int a;
    int b;
};

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

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

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

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