По умолчанию 0u использует подписанный int?

Следующий код вызывает предупреждение о знаковом беззнаковом сравнении:

uint16 x = 5;
if (((x) & (uint16)0x0001u) > 0u) {...}

Замена if на это устраняет предупреждение:

if (((x) & (uint16)0x0001u) > (uint8)0u) {...} //No warning

Разве 0u не должен стать целым числом без знака и не вызвать это предупреждение с самого начала?

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

Some programmer dude 23.08.2024 10:08

Компиляторы имеют тенденцию выдавать предупреждения о коде, который может быть ошибочным по ошибке. Если у вас есть явное приведение типов, как во втором случае, компилятор может разумно решить, что код создан намеренно, и пропустить предупреждение.

MSalters 23.08.2024 10:19

Изменение 0u на 0 также должно устранить предупреждение.

Support Ukraine 23.08.2024 11:29
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
53
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

В вашей системе int имеет более 16 бит. Следовательно, int не только имеет ранг выше, чем uint16_t, но также может представлять любое значение uint16_t. Таким образом, ((x) & (uint16)0x0001u) повышается до int.

Это происходит независимо от другой стороны >. В заголовке ошибочно предполагается, что подписанная сторона — это 0u.

Предупреждение исчезает, вероятно, потому, что компилятор интерпретирует приведение (uint8_t) как указание на то, что сравнение знака и без знака является преднамеренным.

Я не понимаю, здесь: '((x) & (uint16)0x0001u)' обе переменные должны быть равны друг другу, почему в этот момент должно произойти преобразование?

Johan 23.08.2024 11:33

@JohannesLayla: Потому что так гласят правила. И практически ваш процессор может даже не иметь регистра, который мог бы хранить uint16_t.

MSalters 23.08.2024 11:38

Итак, все, что меньше, чем «int», просто преобразуется в это, пока «int» может представлять любое значение переменной? Тогда левая часть конвертируется в int, а 0u преобразуется в беззнаковый int, и это то, что генерирует предупреждение? Итак, я могу либо изменить 0u на 0 (я проверял это, это работает), либо найти, что такое sitze int в моей системе, и преобразовать все в unsigned int?

Johan 23.08.2024 11:49

@JohannesLayla: В качестве совета по стилю я бы просто написала if (x & 1). Это гораздо, гораздо яснее. Я сразу понимаю, что это значит: «если установлен младший бит x». Пока я не написал этот комментарий, я даже не предполагал, что ваша цель настолько проста.

MSalters 23.08.2024 12:06

Я пытался написать только основные части кода. По правде говоря, это массив определений для проверки/установки/очистки определенного бита, который выглядит примерно так: #define IF_BIT_0_SET(x) (((x) & (uint16)0x0001u) > 0u) где шестнадцатеричный код определяет проверяемый бит. Хотя да, поначалу этот момент немного запутан. Но макросы поддерживают 32-битную версию и облегчают чтение кода.

Johan 23.08.2024 12:11

@JohannesLayla: Это только добавляет еще больше нечитабельности. Идиоматическая форма — if (x&1), if (x&2) и т. д. Установка бита по той же причине — x |= 4. Сброс немного сложнее: x &= ~8. Если вам нужно больше битов, (1UL<<31) может быть более читабельным, чем 0x80000000u.

MSalters 23.08.2024 12:21

@JohannesLayla Написание подобных макросов - это запутывание. Программист C должен знать значение & и <<, даже если вы разбудите его посреди ночи. Однако они не знают, что такое IS_BIT_0_SET, потому что это какой-то местный макроязык, изучение которого им, вероятно, не интересно. Таким образом, ваш макрос усложнил чтение кода программистам на C. Мы всегда можем предположить, что читатель нашего кода на языке C является программистом на языке C.

Lundin 23.08.2024 12:38

@KonstantinMakarov Тогда IF_BIT_SET(x, 31) вызывает неопределенное поведение из-за неправильного дизайна макроса. Должно было быть 1u или еще лучше, избавиться от макроса.

Lundin 23.08.2024 12:41

@MSalters Составное присваивание |= является проблематичным: если x представляет собой небольшой целочисленный тип, то вы не сможете избежать неявного преобразования типов. Тогда как x = x | 4 можно было бы переписать, чтобы избавиться от неявных рекламных акций: x = (uint32_t) | 4u;. С составным присваиванием тут ничего не поделаешь.

Lundin 23.08.2024 12:43

На данный момент я не могу изменить интерфейс этих макросов. На данный момент я изменил 0u на 0, это устранило предупреждение. Я не уверен, хочу ли я изменить это, чтобы не использовать макросы, или я хочу изменить/улучшить макросы. Остаются два преимущества макросов: меньше «показываемых» скобок и меньшая вероятность ошибок при вводе. Моей первоначальной целью было уменьшить количество получаемых мной предупреждений (550+), и мне удалось сократить количество этих предупреждений до 14, поскольку они находятся в той части кода, к которой я сейчас не могу прикоснуться.

Johan 23.08.2024 13:40

Re «Предупреждение исчезает, вероятно, потому, что компилятор интерпретирует приведение (uint8_t) как указание на то, что сравнение знака/беззнака является преднамеренным»: Предупреждение исчезает, потому что в ((x) & (uint16)0x0001u) > (uint8)0u оба операнда > являются int.

Eric Postpischil 23.08.2024 13:42
Ответ принят как подходящий

Все в C имеет тип, включая целочисленные константы, такие как 0 и 0u.

В исходном выражении без приведения ((x & 0x0001u) > 0u):

Тогда x — это тип uint16_t, 0x0001u — это тип unsigned int (из-за u), а 0u — это тоже unsigned int.

Далее применяются обычные арифметические преобразования, включающие целочисленное продвижение (см. Правила продвижения неявного типа). В 32- или 64-битной системе (TriCore = 32 бита) uint16_t — это целое число, повышенное до int, поскольку uint16_t — это небольшой целочисленный тип (меньше int).

Тогда левое выражение будет иметь тип int & unsigned int. Затем они балансируются «обычным образом», делая знаковый операнд беззнаковым. Итоговое выражение заканчивается типами unsigned int > unsigned int — одинаковыми типами и беззнаковым сравнением.


В случае ((x) & (uint16)0x0001u) > 0u мы получим то же самое, но теперь 0x0001u явно принудительно преобразуется в uint16_t. В итоге мы получили типы uint16_t & uint16_t. Оба операнда теперь являются целыми числами, повышенными до int, но поскольку они относятся к одному и тому же типу, дальнейшие преобразования не производятся. Итак, слева от > стоит int, а справа остаётся unsigned int. И, таким образом, «сравнение знаков и беззнаков».

(Это не обязательно ошибка, но может быть. Она хрупкая и подвержена ошибкам.)


В случае (x) & (uint16)0x0001u) > (uint8)0u операнды разделяются на разные типы: (uint16 & uint16_t) > uint8_t. Левая часть, как и раньше, повышается до int, но теперь uint8_t тоже является целым числом. Таким образом, вы фактически получаете знаковое сравнение, хотя все операнды были беззнаковыми типами. Наверное, не намерение.


Передовая практика:

  • Избегайте написания выражений, содержащих неявные расширения типов, как указано выше, поскольку они являются тонкими и подвержены ошибкам.

  • По возможности избегайте приведения типов, если вы точно не знаете, что делаете.

    Хотя в 32-битном MCU приведение каждого операнда к int/uint32_t устраняет все опасения по поводу неявных повышений, так что это один из разумных способов справиться с этим для этой конкретной цели.

При работе с микроконтроллерами настоятельно рекомендуется использовать MISRA C/отраслевой стандарт. Большая глава MISRA посвящена неявным ошибкам преобразования, поэтому, применяя правила, вы устраняете такие ошибки.

Спасибо за очень подробный ответ, он объяснил каждый аспект того, почему предупреждение было дано в моем первом фрагменте, а теперь и во втором. Обычно я использую MISRA, но сейчас не могу. Как написано в моем комментарии, я до сих пор не уверен, стоит ли улучшать используемый макрос или вообще не прибегать к нему. Но мой первоначальный вопрос получил окончательный ответ.

Johan 23.08.2024 13:46

@Johan Часть этого может быть приемлема в качестве макроса, например #define BIT_X (1u << 5) будет хорошим макросом, поскольку это всего лишь целочисленное константное выражение времени компиляции. Но #define MASK_BIT_N(data, n) (data & (1u << n)) — это всего лишь запутывание: data & (1u << n) — это уже ясный, читаемый код, а макрос его просто испортил.

Lundin 23.08.2024 13:51

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