Следующий код вызывает предупреждение о знаковом беззнаковом сравнении:
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) — это уже ясный, читаемый код, а макрос его просто испортил.
Пожалуйста, прочитайте об обычных арифметических преобразованиях, что и происходит здесь.