Допустимо ли запись в байтовый массив в объединении и чтение из int для преобразования значений в MISRA C? - PullRequest
3 голосов
/ 24 марта 2020

Полагаю, об этом, должно быть, спрашивали раньше, но я не смог получить конкретный c да / нет ответа.

У меня есть фрагмент кода:

union integer_to_byte
{
    signed int  IntPart;
    unsigned char BytePart[2];
};

typedef union integer_to_byte I2B;

main()
{
   I2B u16VarNo;

   while(1)
   {
       // some code....
       u16VarNo.BytePart[1]= P1;

       // some more code ....
       u16VarNo.BytePart[0]= P2;

       // still more code ...
       if(u16VarNo.IntPart != 0xFFFF)
       {
       }
   }
}

Это законный способ использовать союзы в C? Из того, что я прочитал; действительна только последняя назначенная часть Union.So "u16VarNo.BytePart [1]" не определено? Код, который я написал, работает отлично, как и ожидалось, но я подумал, что уясню его.

TIA.

Ответы [ 4 ]

5 голосов
/ 25 марта 2020

Формально unions недопустимы, хотя это правило было смягчено до Консультативного с MISRA- C: 2004 до MISRA- C: 2012. Основная цель запрета union всегда заключалась в том, чтобы предотвратить действительно глупые вещи, такие как создание «вариантов типов» в виде Visual Basi c или путем повторного использования той же области памяти для несвязанных целей.

Но для использование union в целях наказания типов является обычной практикой, особенно во встраиваемых системах, поэтому запретить их также сложно. Правило 19.2 поднимает действительную проблему, что запись в один член объединения, а затем чтение из другого вызывает неуказанное или определяемое реализацией поведение. Не указывается, если элементы не совпадают, в противном случае определяется реализацией, поскольку существуют преобразования.

Дальнейшие опасения MISRA относительно нарушения правила - это заполнение, выравнивание, порядковый номер и порядок битов (в случае битовых полей) , Это также действительные проблемы - в вашем конкретном c примере многие из них являются потенциальными проблемами.

Мой совет таков:

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

    int16_t some_int = ...;
    uint8_t ms = (uint16_t)some_int >> 8;
    uint8_t ls = some_int & 0xFF;
    
  • Убедитесь, что заполнение не является проблемой (псевдокод) ) _Static_assert( sizeof(the_union) == sizeof(all_members)...

  • Документировать любой код, который запрещает заполнение, как в исходном коде с комментариями, так и в документе реализации MISRA- C. Вещи типа #pragma pack(1) или что-то другое, что использует ваш компилятор c.
5 голосов
/ 25 марта 2020

Допустимо ли записывать байтовый массив в объединение и читать из целого числа для преобразования значений в MISRA C?

Нет. Соединения не должны использоваться.

MISRA C: 2004, 18.4 - Соединения не должны использоваться.
MISRA C: 2012, 19.2 - Ключевое слово union не должно использоваться

Правило в MISRA C:2004 следует следующим образом:

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

и

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

Ваше использование не подходит для этих случаев.

3 голосов
/ 24 марта 2020

Union Punning, особенно с использованием семейства g cc (или IAR, GHS, ARM и многих других компиляторов), на 100% в порядке.

Все известные мне компиляторы следуют сноске 95.

Если элемент, используемый для доступа к содержимому объекта объединения, не совпадает с элементом, который последний раз использовался для хранения значения в объекте, соответствующая часть представления объекта значения повторно интерпретируется как представление объекта в новый тип, как описано в 6.2.6 (процесс, который иногда называют «наказанием типов»). Это может быть представление ловушки.

1 голос
/ 24 марта 2020

В обычный C - только в соответствии с правилами ISO C, а не дополнительными правилами, добавленными MISRA - показанная конструкция соответствует , но не строго соответствует , поскольку зависит от неопределенного поведения . «Unspecified» означает, в этом случае, что чтение из u16VarNo.IntPart может дать вам значение, которое вообще не имеет никакого смысла, но не разрешено взломать sh вашу программу, и компилятор не допускается оптимизация при условии, что чтение никогда не будет выполнено.

Точное правило: C2011, раздел 6.2.6.1, параграф 7 :

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

u16VarNo.BytePart[1]= P1 хранит значение в элементе объекта типа объединения. В этом союзе есть еще два члена, BytePart[0] и IntPart ¹; оба они охватывают по крайней мере один байт представления объекта, который не соответствует BytePart[1] (в зависимости от того, насколько велик signed int); этот байт принимает неопределенное значение при записи в BytePart[1].

Практический результат этого заключается в том, что после

u16VarNo.BytePart[1] = 0xFF;
u16VarNo.BytePart[0] = 0xFF;

вам разрешено читать из uint16VarNo.IntPart, но значение вы вполне можете быть мусором. В частности,

assert(u16VarNo.IntPart == 0xFFFF);   // THIS ASSERTION MAY FAIL

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


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

#include <stdint.h>

int16_t be16_to_cpu_signed(const uint8_t data[static 2])
{
    uint32_t val = (((uint32_t)data[0]) << 8) | 
                   (((uint32_t)data[1]) << 0);
    return ((int32_t) val) - ((int32_t)0x10000);
}

int16_t le16_to_cpu_signed(const uint8_t data[static 2])
{
    uint32_t val = (((uint32_t)data[0]) << 0) | 
                   (((uint32_t)data[1]) << 8);
    return ((int32_t) val) - ((int32_t)0x10000);
}

Есть две функции, потому что вам нужно знать и указать в своем коде, какой порядковый номер внешний источник предоставляет данные. (Это еще одна, не связанная причина, по которой на ваш исходный код нельзя положиться.) Вы должны использовать 32- промежуточный бит без знака, потому что константа 0x10000 не помещается в 16-битный регистр. Вы должны включить все эти явные приведения к типам stdint.h фиксированной ширины, потому что в противном случае «обычные арифметические c преобразования» имеют хороший шанс выбрать неправильную подпись для каждого шага. (Сдвиги и / или должны быть выполнены в беззнаковой арифметике c, а окончательное вычитание в подписанной арифмети c.)


¹ Независимо от того, являются ли BytePart[0] и BytePart[1] двумя отдельными члены союза плохо определены; это пример аргумента «что именно является« объектом »», который не был разрешен со времени первоначальной публикации стандарта 1989 C, несмотря на многочисленные попытки исправить формулировку. Однако небезопасно предполагать, что компиляторы не будут рассматривать их как два отдельных объекта.

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