В обычный 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, несмотря на многочисленные попытки исправить формулировку. Однако небезопасно предполагать, что компиляторы не будут рассматривать их как два отдельных объекта.