Я пишу приложение в c для STM32F105, используя gcc.
В прошлом (с более простыми проектами), я всегда определяются переменные , как char
, int
, unsigned int
и так далее.
Я вижу , что он является общим для использования типы , определенные в stdint.h, такие как int8_t
, uint8_t
, uint32_t
и т.д. Это правда , что в многокорпусных API, который я использую, а также в библиотеке ARM CMSIS от ST.
Я считаю, что понимаю, почему мы должны это делать; чтобы позволить компилятору лучше оптимизировать пространство памяти. Я ожидаю, что могут быть дополнительные причины.
Однако из-за целочисленных правил продвижения в c я продолжаю сталкиваться с предупреждениями преобразования каждый раз, когда пытаюсь добавить два значения, выполнить побитовую операцию и т. Д. Предупреждение выглядит примерно так conversion to 'uint16_t' from 'int' may alter its value [-Wconversion]
. Вопрос обсуждается здесь и здесь .
Это не происходит при использовании переменных, объявленных как int
или unsigned int
.
Чтобы дать пару примеров, учитывая это:
uint16_t value16;
uint8_t value8;
Я должен был бы изменить это:
value16 <<= 8;
value8 += 2;
к этому:
value16 = (uint16_t)(value16 << 8);
value8 = (uint8_t)(value8 + 2);
Это некрасиво, но я могу сделать это при необходимости. Вот мои вопросы:
Есть ли случай, когда преобразование из неподписанного в подписанное и обратно в неподписанное приведет к неверному результату?
Существуют ли другие серьезные причины для / против использования целочисленных типов stdint.h?
Судя по полученным ответам, похоже, что типы stdint.h, как правило, предпочтительнее, хотя c преобразует uint
в int
и обратно. Это приводит к большему вопросу:
- Я могу предотвратить предупреждения компилятора, используя приведение типов (например
value16 = (uint16_t)(value16 << 8);
). Я просто скрываю проблему? Есть ли лучший способ сделать это?
8u
и2u
.value8 += 2u;
и другоеvalue8 = value8 + 2u;
, но получаю одинаковые предупреждения.Ответы:
Соответствующий стандартам компилятор, где
int
было от 17 до 32 бит, может законно делать все, что захочет, с помощью следующего кода:Реализация, которая хотела сделать это, могла законно генерировать программу, которая ничего не делала бы, кроме как выводить строку «Fred» на каждом выводе порта, используя каждый мыслимый протокол. Вероятность того, что программа будет перенесена в реализацию, которая могла бы сделать такую вещь, исключительно мала, но теоретически возможна. Если хотите, чтобы хотел написать приведенный выше код, чтобы гарантировать, что он не будет участвовать в неопределенном поведении, необходимо написать последнее выражение как
(uint32_t)x*x
или1u*x*x
. В компиляторе, гдеint
между 17 и 31 битами, последнее выражение будет обрезать верхние биты, но не будет участвовать в неопределенном поведении.Я думаю, что предупреждения gcc, вероятно, пытаются предположить, что написанный код не является полностью переносимым на 100%. Бывают случаи, когда код действительно должен быть написан, чтобы избежать поведения, которое было бы неопределенным в некоторых реализациях, но во многих других случаях нужно просто предположить, что код вряд ли будет использоваться в реализациях, которые будут чрезмерно раздражать.
Обратите внимание, что использование таких типов, как
int
иshort
может устранить некоторые предупреждения и исправить некоторые проблемы, но, скорее всего, создаст другие. Взаимодействие между такими типами, какuint16_t
C и правилами целочисленного продвижения, является странным, но такие типы все еще, вероятно, лучше, чем любая другая альтернатива.источник
1) Если вы просто переводите целое число без знака в целое число со знаком одинаковой длины, без каких-либо промежуточных операций, вы будете получать один и тот же результат каждый раз, так что здесь нет проблем. Но различные логические и арифметические операции по-разному действуют на операнды со знаком и без знака.
2) Основная причина использования
stdint.h
типов заключается в том, что размер битов таких типов определен и одинаков для всех платформ, что не соответствует действительностиint
иlong
т. Д., А такжеchar
не имеет стандартного значения, он может быть подписан или не подписан дефолт. Это облегчает манипулирование данными, зная точный размер, без дополнительных проверок и допущений.источник
int32_t
иuint32_t
равны по всей платформе, на которой они определены . Если процессор не имеет точно совпадающий тип оборудования, эти типы не определены. Отсюда преимуществоint
и т. Д. И, возможно,int_least32_t
и т. Д.Поскольку Евгений № 2, вероятно, самый важный момент, я просто хотел бы добавить, что это
Также Джек Гэнсл, кажется, поддерживает это правило: http://www.ganssle.com/tem/tem265.html
источник
uint32_t
.Простой способ устранить предупреждения - избегать использования -Wconversion в GCC. Я думаю, что вы должны включить эту опцию вручную, но если нет, вы можете использовать -Wno-преобразование, чтобы отключить ее. Вы можете включить предупреждения для прецизионных преобразований знаков и FP с помощью других параметров , если они все еще нужны.
Предупреждения -Wconversion почти всегда являются ложными срабатываниями, поэтому, возможно, даже -Wextra не включает их по умолчанию. У вопроса переполнения стека есть много предложений для хороших наборов опций. Исходя из моего собственного опыта, это хорошее место для начала:
Добавьте больше, если они вам нужны, но, скорее всего, вы этого не сделаете.
Если вам нужно сохранить -Wconversion, вы можете немного сократить свой код, просто введя числовой операнд:
Однако это не так легко прочитать без подсветки синтаксиса.
источник
В любом программном проекте очень важно использовать определения переносимых типов. (даже следующая версия того же компилятора нуждается в этом рассмотрении.) Хороший пример: несколько лет назад я работал над проектом, в котором текущий компилятор определил 'int' как 8 бит. Следующая версия компилятора определила 'int' как 16 бит. Поскольку мы не использовали переносимых определений для «int», ram (фактически) удвоился в размере, и многие кодовые последовательности, которые зависели от 8-битного int, потерпели неудачу. Использование определения переносимого типа позволило бы избежать этой проблемы (сотни человеко-часов для исправления).
источник
int
для ссылки на 8-битный тип. Даже если компилятор без C, такой как CCS, делает это, разумный код должен использовать либоchar
тип с определением типа для 8 битов, либо тип с определением типа (не "long") для 16 битов. С другой стороны, перенос кода из чего-то вроде CCS в реальный компилятор может быть проблематичным, даже если он использует правильные определения типов, поскольку такие компиляторы часто «необычны» в других отношениях.Да. N-разрядное целое число со знаком может представлять примерно половину числа неотрицательных чисел как n-разрядное целое число без знака, и полагаться на характеристики переполнения - неопределенное поведение, поэтому может произойти все что угодно. Подавляющее большинство современных и прошлых процессоров используют два дополнения, поэтому многие операции выполняют одно и то же для целочисленных типов со знаком и без знака, но даже тогда не все операции будут давать побитовые идентичные результаты. Позже вы действительно напрашиваетесь на дополнительные проблемы, когда не можете понять, почему ваш код работает не так, как задумано.
Хотя int и unsigned имеют размеры, определенные реализацией, они часто выбираются «разумно» реализацией либо из соображений размера, либо из соображений скорости. Я обычно придерживаюсь этого, если у меня нет веских причин поступать иначе. Аналогично, при рассмотрении вопроса о том, использовать ли int или unsigned, я обычно предпочитаю int, если только у меня нет веских причин сделать это иначе.
В тех случаях, когда мне действительно нужно лучше контролировать размер или подпись типа, я обычно предпочитаю либо использовать системный typedef (size_t, intmax_t и т. Д.), Либо сделать свой собственный typedef, который указывает на функцию данного тип (prng_int, adc_int и т. д.).
источник
Часто код используется в ARM thumb и AVR (а также в x86, powerPC и других архитектурах), а 16 или 32-разрядная версия может быть более эффективной (в обоих случаях: флэш-память и циклы) в ARM STM32 даже для переменной, которая умещается в 8 бит ( 8 бит более эффективен на AVR) . Однако если SRAM почти заполнен, возврат к 8-битному для глобальных переменных может быть целесообразным (но не для локальных переменных). Для переносимости и обслуживания (особенно для 8-битных переменных) имеет свои преимущества (без каких-либо недостатков) указание МИНИМАЛЬНОГО подходящего размера вместо точного размера и typedef в одном месте .h (обычно в ifdef) для настройки (возможно, uint_fast8_t / uint_least8_t) во время портирования / сборки, например:
Библиотека GNU немного помогает, но обычно определения типов имеют смысл:
// но uint_fast8_t для ОБА, когда SRAM не имеет значения.
источник