Следующий код вызывает предупреждение о знаковом беззнаковом сравнении:
uint16 x = 5;
if (((x) & (uint16)0x0001u) > 0u) {...}
Замена if на это устраняет предупреждение:
if (((x) & (uint16)0x0001u) > (uint8)0u) {...} //No warning
Разве 0u не должен стать целым числом без знака и не вызвать это предупреждение с самого начала?
Компиляторы имеют тенденцию выдавать предупреждения о коде, который может быть ошибочным по ошибке. Если у вас есть явное приведение типов, как во втором случае, компилятор может разумно решить, что код создан намеренно, и пропустить предупреждение.
Изменение 0u
на 0
также должно устранить предупреждение.
В вашей системе int
имеет более 16 бит. Следовательно, int
не только имеет ранг выше, чем uint16_t
, но также может представлять любое значение uint16_t
. Таким образом, ((x) & (uint16)0x0001u)
повышается до int
.
Это происходит независимо от другой стороны >
. В заголовке ошибочно предполагается, что подписанная сторона — это 0u
.
Предупреждение исчезает, вероятно, потому, что компилятор интерпретирует приведение (uint8_t)
как указание на то, что сравнение знака и без знака является преднамеренным.
Я не понимаю, здесь: '((x) & (uint16)0x0001u)' обе переменные должны быть равны друг другу, почему в этот момент должно произойти преобразование?
@JohannesLayla: Потому что так гласят правила. И практически ваш процессор может даже не иметь регистра, который мог бы хранить uint16_t
.
Итак, все, что меньше, чем «int», просто преобразуется в это, пока «int» может представлять любое значение переменной? Тогда левая часть конвертируется в int, а 0u преобразуется в беззнаковый int, и это то, что генерирует предупреждение? Итак, я могу либо изменить 0u на 0 (я проверял это, это работает), либо найти, что такое sitze int в моей системе, и преобразовать все в unsigned int?
@JohannesLayla: В качестве совета по стилю я бы просто написала if (x & 1)
. Это гораздо, гораздо яснее. Я сразу понимаю, что это значит: «если установлен младший бит x». Пока я не написал этот комментарий, я даже не предполагал, что ваша цель настолько проста.
Я пытался написать только основные части кода. По правде говоря, это массив определений для проверки/установки/очистки определенного бита, который выглядит примерно так: #define IF_BIT_0_SET(x) (((x) & (uint16)0x0001u) > 0u)
где шестнадцатеричный код определяет проверяемый бит. Хотя да, поначалу этот момент немного запутан. Но макросы поддерживают 32-битную версию и облегчают чтение кода.
@JohannesLayla: Это только добавляет еще больше нечитабельности. Идиоматическая форма — if (x&1)
, if (x&2)
и т. д. Установка бита по той же причине — x |= 4
. Сброс немного сложнее: x &= ~8
. Если вам нужно больше битов, (1UL<<31)
может быть более читабельным, чем 0x80000000u
.
@JohannesLayla Написание подобных макросов - это запутывание. Программист C должен знать значение &
и <<
, даже если вы разбудите его посреди ночи. Однако они не знают, что такое IS_BIT_0_SET
, потому что это какой-то местный макроязык, изучение которого им, вероятно, не интересно. Таким образом, ваш макрос усложнил чтение кода программистам на C. Мы всегда можем предположить, что читатель нашего кода на языке C является программистом на языке C.
@KonstantinMakarov Тогда IF_BIT_SET(x, 31)
вызывает неопределенное поведение из-за неправильного дизайна макроса. Должно было быть 1u
или еще лучше, избавиться от макроса.
@MSalters Составное присваивание |=
является проблематичным: если x
представляет собой небольшой целочисленный тип, то вы не сможете избежать неявного преобразования типов. Тогда как x = x | 4
можно было бы переписать, чтобы избавиться от неявных рекламных акций: x = (uint32_t) | 4u;
. С составным присваиванием тут ничего не поделаешь.
На данный момент я не могу изменить интерфейс этих макросов. На данный момент я изменил 0u
на 0
, это устранило предупреждение. Я не уверен, хочу ли я изменить это, чтобы не использовать макросы, или я хочу изменить/улучшить макросы. Остаются два преимущества макросов: меньше «показываемых» скобок и меньшая вероятность ошибок при вводе. Моей первоначальной целью было уменьшить количество получаемых мной предупреждений (550+), и мне удалось сократить количество этих предупреждений до 14, поскольку они находятся в той части кода, к которой я сейчас не могу прикоснуться.
Re «Предупреждение исчезает, вероятно, потому, что компилятор интерпретирует приведение (uint8_t)
как указание на то, что сравнение знака/беззнака является преднамеренным»: Предупреждение исчезает, потому что в ((x) & (uint16)0x0001u) > (uint8)0u
оба операнда >
являются int
.
Все в 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 Часть этого может быть приемлема в качестве макроса, например #define BIT_X (1u << 5)
будет хорошим макросом, поскольку это всего лишь целочисленное константное выражение времени компиляции. Но #define MASK_BIT_N(data, n) (data & (1u << n))
— это всего лишь запутывание: data & (1u << n)
— это уже ясный, читаемый код, а макрос его просто испортил.
Пожалуйста, прочитайте об обычных арифметических преобразованиях, что и происходит здесь.