Следует ли использовать приведение для усечения длинной переменной?

У меня есть 16-битная беззнаковая переменная. Мне нужно разделить его на 8-битные куски.

Достаточно ли сделать следующее:

chunk_lsb = (uint8)variable;
chunk_msb = (uint8)(variable >> 8);

Или я должен использовать маску:

chunk_lsb = (uint8)(variable & 0xFFu);
chunk_msb = (uint8)((variable >> 8) & 0xFFu);

Я знаю, что оба подхода работают, я просто ищу лучший способ сделать это, если он есть. Может быть, их нет, и лучше всего использовать приведение для уменьшения вычислений? Что вы думаете, ребята?

Я думаю, что оба будут генерировать один и тот же двоичный код. Поскольку первое решение более читабельно, я бы использовал это. Вам просто нужно быть уверенным, что uint8 - это 8-но на всех платформах (я думаю, что это так)

jaudo 25.02.2019 13:47

Спасибо, @jaudo, я тоже в этом направлении.

Soyding Mete 25.02.2019 13:54

Примечание: следует отдавать предпочтение стандартным stdint.h типам, таким как uint8_t, а не создавать свои собственные.

user694733 25.02.2019 14:01
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
9
3
1 197
4

Ответы 4

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?
Andrew Henle 25.02.2019 14:05

ИМО, если цель состоит в том, чтобы замаскировать, маска слова понятнее, чем любое (магическое) число.

Jose 25.02.2019 14:44
ИМО, если цель состоит в том, чтобы замаскировать, маска слова понятнее, чем любое (магическое) число А? Сколько бит маскирует: unsigned int result = input & mask; И нет, я не DV.
Andrew Henle 25.02.2019 14:57
0xFF vs one_byte_bit_mask_0xFF vs mask, я выбираю второй вариант как наименее плохой.
Jose 25.02.2019 15:04

Поскольку uint8 не имеет знака, вы не используете имеют для маскирования:

6.3.1.3 Signed and unsigned integers

  1. 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.
  2. 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)
  3. 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 имеет неприятное целочисленное продвижение, поэтому, если вы попытаетесь сделать то же самое со сдвигом влево, программа может взорваться в неопределенном поведении. В данном конкретном случае мы уходим, так как это сдвиг положительного числа вправо.

Lundin 25.02.2019 14:04

Кроме того, бросок делает потерю точности явно преднамеренной. Многие компиляторы могут выдавать предупреждение о потере точности, что может быть крайне важно. (Я думаю о нубах, которые настолько определенный, что «size_t и указатели на самом деле просто unsigned int...)

Andrew Henle 25.02.2019 14:08

@Lundin: в C89 поведение сдвига влево со знаком было определено для всех возможных значений левого операнда в реализациях, целочисленные типы которых не имеют битов заполнения или представлений ловушки, но ловушка могла бы быть более разумной в ситуациях, когда обязательное поведение будет отличаться от умножения в степени двойки. Я не видел никаких доказательств того, что предполагается какое-либо намерение изменить обработку ситуаций, когда поведение C89 и умножение в степени двойки были бы эквивалентны.

supercat 25.02.2019 18:44

@supercat Достаточно плохо, чтобы случайно сместить данные в бит знака, независимо от формата UB и подписи.

Lundin 25.02.2019 20:16

@Lundin: В нотации с дополнением до двух знаковый бит - это просто сокращение для «состояния этого бита и бесконечного числа битов слева». Значение -3 представляет собой бесконечное число единиц, за которыми следует 01. Возможно, удобнее думать о 32-разрядном значении с дополнением до двух как о составленном из бесконечного числа нулей или единиц, за которыми следует ровно 31 бит, но бесконечное количество единиц, за которыми следуют еще 29, а затем 01, то же самое, что бесконечное количество единиц, за которыми следует 01.

supercat 25.02.2019 20:42

@Lundin: единственный раз, когда может возникнуть какая-либо аномалия, будет в случаях, когда сдвиг весь номер, включая начальные единицы или нули, приведет к чему-то, что не имеет формы «бесконечное количество единиц или нулей, за которыми следует 31 отдельный биты". Я не уверен, почему вы думаете, что есть что-то «случайное»: о смещении части бесконечной последовательности единиц в бит, представляющий бесконечную последовательность единиц.

supercat 25.02.2019 20:53

@supercat Потому что на 16-битной машине многие программисты считают, что uint8_t u8=0x80; ... u8 << 8; даст результат 32768, а не -32768. И что (u8<<8) >>8 даст 0x80, а не 0xFF80. Это известный источник багов, так что спорить нет смысла.

Lundin 26.02.2019 08:16

@Lundin: В этом сценарии поведение будет отличаться от умножения степени двойки. Какое это имеет отношение к сдвигу влево отрицательных чисел в случаях, когда поведение C89 было бы эквивалентно умножению степени двойки?

supercat 26.02.2019 08:47

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

Soyding Mete 26.02.2019 08:31

Непонятно, какой это тип 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 бит (что, как я полагаю, гарантирует стандарт).

Ilmari Karonen 25.02.2019 16:37

@IlmariKaronen Размер переменной для этого не имеет значения, только ее подпись. Если значение отрицательное, вы получаете поведение, определяемое реализацией. Одним из примеров такого сломанного кода может быть unsigned char u = 0; ... ~u >> n;

Lundin 25.02.2019 16:41

Я согласен с тем, что это непредсказуемо, поскольку стандарт не требует, чтобы определяемый реализацией результат сдвига вправо отрицательного целого числа со знаком был либо арифметическим или логическим сдвигом. Однако в реализациях, где является один из этих двух, сдвиг вправо на биты н, а затем маскирование (как минимум) старших битов н должно давать один и тот же результат независимо от типа сдвига. Таким образом, ваше вводное замечание в третьем абзаце вашего ответа кажется немного вводящим в заблуждение.

Ilmari Karonen 25.02.2019 17:18

@Lundin, возможно, стоит отметить, что ваш первый набор рекомендаций предполагает, что chunk_lsb и chunk_msb сами по себе имеют тип, эквивалентный uint8_t. Это неясно из вопроса - «8-битные фрагменты» OP могут храниться в переменных, имеющих более широкий тип или подписанный.

John Bollinger 25.02.2019 17:29

@IlmariKaronen: Если бы авторы Стандарта требовали поведения с нулевым заполнением или расширением знака, но существовала некоторая платформа, где какое-то другое поведение было бы более полезным, обязательное поведение с нулевым заполнением или расширением знака могло бы сделать C менее полезно на этой платформе. Единственные ситуации, когда было бы лучше, если бы Стандарт предписывал эти два конкретных поведения, были бы, если такая платформа действительно существует, или создатель компилятора чувствует себя намеренно странным. Первый случай лучше всего подходит для нет, определяющего поведение...

supercat 25.02.2019 18:38

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

supercat 25.02.2019 18:40

Не согласен с использованием (uint8_t) применить и маску в chunk_lsb = (uint8_t) (variable & 0xFFu);. Приведения всегда достаточно, чтобы заглушить предупреждения. Маска лучше программирует. Но оба ВЛАЖНЫЙ. Кроме того, мне очень нравится ответ.

chux - Reinstate Monica 26.02.2019 03:07

@Lundin, Джон Боллинджер: я действительно точно указал, что это переменные неподписанный, и в этом конкретном случае chunk_lsb и chunk_msb являются uint8 (uint8_t, отсюда и приведение), извините, если это было неясно

Soyding Mete 26.02.2019 09:06

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