У меня есть 16-битная беззнаковая переменная. Мне нужно разделить его на 8-битные куски.
Достаточно ли сделать следующее:
chunk_lsb = (uint8)variable;
chunk_msb = (uint8)(variable >> 8);
Или я должен использовать маску:
chunk_lsb = (uint8)(variable & 0xFFu);
chunk_msb = (uint8)((variable >> 8) & 0xFFu);
Я знаю, что оба подхода работают, я просто ищу лучший способ сделать это, если он есть. Может быть, их нет, и лучше всего использовать приведение для уменьшения вычислений? Что вы думаете, ребята?
Спасибо, @jaudo, я тоже в этом направлении.
Примечание: следует отдавать предпочтение стандартным stdint.h типам, таким как uint8_t, а не создавать свои собственные.





Maybe there's none and just use the cast to reduce calculations is the best way?
Вообще говоря, ассемблерный код будет таким же, поэтому с точки зрения скорости не имеет значения, какой из них вы используете:
What do you guys think?
ИМО, первый более понятен с точки зрения удобочитаемости, но я не могу определить стандарт кодирования или руководство, которое поддерживает мои предпочтения. В любом случае, я бы использовал переменную const, если вы предпочитаете второе, чтобы удалить магические числа и прояснить, что целью является маскировка (при условии, что вы выбрали правильное имя для константной переменной).
const, если вы предпочитаете вторую, чтобы удалить магические числа и прояснить, что целью является маскировка. Любой, кто думает, что 0xFF в контексте битовой маскировки — это «магическое число», которое нужно замаскировать переменной, занимается карго-культовым программированием. Какая возможная переменная может быть более понятной, чем 0xFF в качестве битовой маски? one_byte_bit_mask_0xFF?
ИМО, если цель состоит в том, чтобы замаскировать, маска слова понятнее, чем любое (магическое) число.
unsigned int result = input & mask; И нет, я не DV.
0xFF vs one_byte_bit_mask_0xFF vs mask, я выбираю второй вариант как наименее плохой.
Поскольку uint8 не имеет знака, вы не используете имеют для маскирования:
6.3.1.3 Signed and unsigned integers
- When a value with integer type is converted to another integer type other than _ Bool , if the value can be represented by the new type, it is unchanged.
- Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type.60)
- Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised.
Однако, скорее всего, оба варианта приведут к одному и тому же выводу компилятора. Обычно я добавляю маску, потому что она ясно показывает, что должен делать код, и делает приведение типов ненужным.
За исключением того, что C имеет неприятное целочисленное продвижение, поэтому, если вы попытаетесь сделать то же самое со сдвигом влево, программа может взорваться в неопределенном поведении. В данном конкретном случае мы уходим, так как это сдвиг положительного числа вправо.
Кроме того, бросок делает потерю точности явно преднамеренной. Многие компиляторы могут выдавать предупреждение о потере точности, что может быть крайне важно. (Я думаю о нубах, которые настолько определенный, что «size_t и указатели на самом деле просто unsigned int...)
@Lundin: в C89 поведение сдвига влево со знаком было определено для всех возможных значений левого операнда в реализациях, целочисленные типы которых не имеют битов заполнения или представлений ловушки, но ловушка могла бы быть более разумной в ситуациях, когда обязательное поведение будет отличаться от умножения в степени двойки. Я не видел никаких доказательств того, что предполагается какое-либо намерение изменить обработку ситуаций, когда поведение C89 и умножение в степени двойки были бы эквивалентны.
@supercat Достаточно плохо, чтобы случайно сместить данные в бит знака, независимо от формата UB и подписи.
@Lundin: В нотации с дополнением до двух знаковый бит - это просто сокращение для «состояния этого бита и бесконечного числа битов слева». Значение -3 представляет собой бесконечное число единиц, за которыми следует 01. Возможно, удобнее думать о 32-разрядном значении с дополнением до двух как о составленном из бесконечного числа нулей или единиц, за которыми следует ровно 31 бит, но бесконечное количество единиц, за которыми следуют еще 29, а затем 01, то же самое, что бесконечное количество единиц, за которыми следует 01.
@Lundin: единственный раз, когда может возникнуть какая-либо аномалия, будет в случаях, когда сдвиг весь номер, включая начальные единицы или нули, приведет к чему-то, что не имеет формы «бесконечное количество единиц или нулей, за которыми следует 31 отдельный биты". Я не уверен, почему вы думаете, что есть что-то «случайное»: о смещении части бесконечной последовательности единиц в бит, представляющий бесконечную последовательность единиц.
@supercat Потому что на 16-битной машине многие программисты считают, что uint8_t u8=0x80; ... u8 << 8; даст результат 32768, а не -32768. И что (u8<<8) >>8 даст 0x80, а не 0xFF80. Это известный источник багов, так что спорить нет смысла.
@Lundin: В этом сценарии поведение будет отличаться от умножения степени двойки. Какое это имеет отношение к сдвигу влево отрицательных чисел в случаях, когда поведение C89 было бы эквивалентно умножению степени двойки?
Should a cast be used to truncate a long variable?
Если chunk_lsb — 8-битный объект (уже, чем variable), используйте приведение или маска (не оба). Полезно для подавления педантичных предупреждений об уменьшении диапазона. Я предпочитаю маску - если только компилятор не привередлив.
uint8_t chunk_lsb = (uint8_t) variable;
// or
uint8_t chunk_lsb = variable & 0xFFu;
В противном случае используйте маску.
unsigned chunk_lsb = variable & 0xFFu;
Это и ваш комментарий об использовании обоих решений WET удовлетворяют, спасибо @chux
Непонятно, какой это тип variable. Без указанного, мы можем только строить догадки.
Но в целом следует избегать сдвига битов в целочисленных типах со знаком, так как это приводит к различным формам плохо определенного поведения. Это, в свою очередь, означает, что вы должны быть осторожны и с небольшими целочисленными типами, потому что они повышаются до подписанных int. См. Правила продвижения неявного типа.
Конкретный случай (uint8)((variable >> 8) & 0xFFu); безопасен, если variable не имеет знака. В противном случае это небезопасно, поскольку сдвиг отрицательного значения вправо приводит к поведению, определяемому реализацией (арифметическому или логическому сдвигу).
variable << 8 вызовет неопределенное поведение в 16-битных системах, если variable является небольшим целочисленным типом или int16_t.
Таким образом, самый безопасный и портативный способ, независимо от смещения влево/вправо, таков:
chunk_lsb = variable;
chunk_msb = ((unsigned int)variable >> 8);
Хотя вы можете захотеть быть слишком явным, чтобы отключить все предупреждения компилятора:
chunk_lsb = (uint8_t) (variable & 0xFFu);
chunk_msb = (uint8_t) ( (unsigned int)variable>>8 & 0xFFu );
AFAICT (variable >> 8) & 0xFFu даст один и тот же результат независимо от того, является ли сдвиг арифметическим или логическим, если int имеет ширину не менее 16 бит (что, как я полагаю, гарантирует стандарт).
@IlmariKaronen Размер переменной для этого не имеет значения, только ее подпись. Если значение отрицательное, вы получаете поведение, определяемое реализацией. Одним из примеров такого сломанного кода может быть unsigned char u = 0; ... ~u >> n;
Я согласен с тем, что это непредсказуемо, поскольку стандарт не требует, чтобы определяемый реализацией результат сдвига вправо отрицательного целого числа со знаком был либо арифметическим или логическим сдвигом. Однако в реализациях, где является один из этих двух, сдвиг вправо на биты н, а затем маскирование (как минимум) старших битов н должно давать один и тот же результат независимо от типа сдвига. Таким образом, ваше вводное замечание в третьем абзаце вашего ответа кажется немного вводящим в заблуждение.
@Lundin, возможно, стоит отметить, что ваш первый набор рекомендаций предполагает, что chunk_lsb и chunk_msb сами по себе имеют тип, эквивалентный uint8_t. Это неясно из вопроса - «8-битные фрагменты» OP могут храниться в переменных, имеющих более широкий тип или подписанный.
@IlmariKaronen: Если бы авторы Стандарта требовали поведения с нулевым заполнением или расширением знака, но существовала некоторая платформа, где какое-то другое поведение было бы более полезным, обязательное поведение с нулевым заполнением или расширением знака могло бы сделать C менее полезно на этой платформе. Единственные ситуации, когда было бы лучше, если бы Стандарт предписывал эти два конкретных поведения, были бы, если такая платформа действительно существует, или создатель компилятора чувствует себя намеренно странным. Первый случай лучше всего подходит для нет, определяющего поведение...
... и последний случай вообще не мог быть с пользой приспособлен, поэтому не было никакой необходимости тратить время на этот вопрос.
Не согласен с использованием (uint8_t) применить и маску в chunk_lsb = (uint8_t) (variable & 0xFFu);. Приведения всегда достаточно, чтобы заглушить предупреждения. Маска лучше программирует. Но оба ВЛАЖНЫЙ. Кроме того, мне очень нравится ответ.
@Lundin, Джон Боллинджер: я действительно точно указал, что это переменные неподписанный, и в этом конкретном случае chunk_lsb и chunk_msb являются uint8 (uint8_t, отсюда и приведение), извините, если это было неясно
Я думаю, что оба будут генерировать один и тот же двоичный код. Поскольку первое решение более читабельно, я бы использовал это. Вам просто нужно быть уверенным, что uint8 - это 8-но на всех платформах (я думаю, что это так)