Есть ли причина не использовать целочисленные типы с фиксированной шириной (например, uint8_t)?

Предполагая, что вы используете компилятор, который поддерживает C99 (или даже просто stdint.h), есть ли причина не использовать целочисленные типы фиксированной ширины, такие как uint8_t?

Одна из причин, по которой я знаю, заключается в том, что имеет больше смысла использовать chars при работе с персонажами вместо использования (u)int8_ts, как уже упоминалось в этом вопросе.

Но если вы планируете хранить число, когда вы захотите использовать тип, который вы не знаете, насколько он велик? Т.е. в какой ситуации вы хотите сохранить номер в unsigned short не зная, если это 8, 16 или даже 32 бит, вместо использования uint16t?

Исходя из этого, считается ли более эффективной практикой использование целых чисел фиксированной ширины или использование нормальных целочисленных типов и просто никогда не принимать что-либо и использовать sizeof Где вам нужно знать, сколько байтов они используют?

5 ответов

Решение

На самом деле довольно часто хранить число без необходимости знать точный размер шрифта. В моих программах есть множество величин, которые, как я могу разумно предположить, не превысят 2 миллиардов, или не приведут в исполнение их. Но это не значит, что мне нужен точный 32-битный тип для их хранения, любой тип, который может рассчитывать как минимум на 2 миллиарда, мне подходит.

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

На реализации C99, где CHAR_BIT больше, чем 8 здесь нет int8_t, Стандарт запрещает его существование, потому что он должен иметь биты заполнения, и intN_t типы определены как не имеющие битов заполнения (7.18.1.1/1). uint8_t поэтому также запрещено, потому что (спасибо, аааа) реализация не может определить uint8_t без int8_t,

Таким образом, в очень переносимом коде, если вам нужен тип со знаком, способный содержать значения до 127, вам следует использовать один из signed char, int, int_least8_t или же int_fast8_t в зависимости от того, хотите ли вы попросить компилятор сделать это:

  • работа в с89 (signed char или же int)
  • избегать удивительных целочисленных повышений в арифметических выражениях (int)
  • маленький (int_least8_t или же signed char)
  • быстро (int_fast8_t или же int)

То же самое относится к типу без знака до 255, с unsigned char, unsigned int, uint_least8_t а также uint_fast8_t,

Если вам нужна арифметика по модулю 256 в очень переносимом коде, то вы можете взять модуль самостоятельно, замаскировать биты или играть в игры с битовыми полями.

На практике большинству людей никогда не нужно писать код, который переносим. В данный момент CHAR_BIT > 8 только подходит для специального оборудования, и ваш универсальный код не будет использоваться на нем. Конечно, это может измениться в будущем, но если это произойдет, я подозреваю, что существует так много кода, который делает предположения о Posix и / или Windows (оба из которых гарантируют CHAR_BIT == 8), что работа с непереносимостью вашего кода будет одной небольшой частью больших усилий по переносу кода на эту новую платформу. Любая такая реализация, вероятно, будет беспокоиться о том, как подключиться к Интернету (который имеет дело с октетами), задолго до того, как она будет беспокоиться о том, как заставить ваш код работать и работать:-)

Если вы предполагаете, что CHAR_BIT == 8 в любом случае, тогда я не думаю, что есть какая-то конкретная причина, чтобы избежать (u)int8_t кроме того, если вы хотите, чтобы код работал в C89. Даже в C89 не так сложно найти или написать версию stdint.h для конкретной реализации. Но если вы можете легко написать свой код, требующий, чтобы тип мог содержать 255вместо того, чтобы требовать, чтобы это не могло держать 256тогда вы могли бы также избежать зависимости от CHAR_BIT == 8,

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

Например, данная декларация uint32_t i;поведение выражения (i-1) > 5 когда i ноль будет меняться в зависимости от того, является ли uint32_t меньше чем int, В системах, где, например, int составляет 64 бита (и uint32_t это что-то вроде long short), переменная i будет повышен до int; вычитание и сравнение будут выполнены как подписанные (-1 меньше 5). В системах, где int 32 бита, вычитание и сравнение будут выполнены как unsigned int (вычитание даст действительно большое число, которое больше пяти).

Я не знаю, сколько кода основано на том факте, что промежуточные результаты выражений, включающих в себя неподписанные типы, требуются для переноса даже в отсутствие типов типов (ИМХО, если бы было необходимо поведение с переносом, программист должен был включить типирование) (uint32_t)(i-1) > 5) но стандарт в настоящее время не позволяет Интересно, какие проблемы возникли бы, если бы правило, по крайней мере, позволяло компилятору преобразовывать операнды в более длинный целочисленный тип при отсутствии приведения типов или приведения типов [например, заданного uint32_t i,j, назначение как j = (i+=1) >> 1; потребуется отрубить переполнение, как бы j = (uint32_t)(i+1) >> 1;, но j = (i+1)>>1 не будет]? Или, если на то пошло, как трудно было бы производителям компиляторов гарантировать, что любое выражение целочисленного типа, промежуточные результаты которого могут вписаться в самый большой тип со знаком и не влекут за собой правильные сдвиги на непостоянные величины, даст одинаковое результаты, как если бы все расчеты были выполнены по этому типу? Мне кажется довольно неприглядным, что на машине, где int 32 бита:

  uint64_t a, b, c;...
  a & = ~ 0x40000000;
  b & = ~ 0x80000000;
  с &= ~0x100000000;

очищает один бит каждый из a а также c, но очищает верхние 33 бита b; большинство компиляторов не дадут намека на то, что во втором выражении что-то "другое"

Это правда, что ширина стандартного целочисленного типа может изменяться от одной платформы к другой, но не ее минимальная ширина.

Например, Стандарт C определяет, что int по крайней мере 16-bit и long по крайней мере 32-bit широкий.

Если у вас нет каких-либо ограничений по размеру при хранении ваших объектов, вы можете позволить это реализовать. Например, если ваше максимальное значение со знаком будет соответствовать 16-bit Вы можете просто использовать int, Затем вы позволяете реализации иметь окончательное слово о том, что является естественным int ширина для архитектуры, на которую ориентирована реализация.

Код должен раскрыть случайному читателю (и самому программисту), что важно. Это просто какое-то целое число или целое число без знака или даже целое число со знаком. То же самое касается размера. Действительно ли для алгоритма важно, чтобы какая-то переменная была по умолчанию 16-битной? Или это просто ненужное микроуправление и неудачная попытка оптимизации?

Это то, что делает программирование искусством - показать, что важно.

Вы должны использовать только фиксированные типы ширины, когда делаете предположение о ширине.

uint8_t а также unsigned char одинаковы на большинстве платформ, но не на всех. С помощью uint8_t подчеркивает тот факт, что вы предполагаете, что архитектура с 8 бит char и не будет компилироваться на других, так что это особенность.

В противном случае я бы использовал "семантический" typedef такие как size_t, uintptr_t, ptrdiff_t потому что они гораздо лучше отражают то, что вы имеете в виду с данными. Я почти никогда не использую базовые типы напрямую, int только для ошибок, и я не помню, чтобы когда-либо использовал short,

Изменить: После тщательного прочтения C11 я заключаю, что uint8_t, если он существует, должен быть unsigned char и не может быть просто char даже если этот тип без знака. Это вытекает из требования в 7.20.1 р1, что все intN_t а также uintN_t должны быть соответствующие подписанные и неподписанные типы. Единственная такая пара для типов символов signed char а также unsigned char,

Есть много причин, по которым можно было бы использовать, назовем их семантическими типами, напримерint или char по типам фиксированной ширины, таким как uint8_t:

Соответствие существующему API

Стандартная библиотека C использует char*везде. Зачем вводить пользователей в заблуждение (и вводить возможные ошибки?), Используя другой тип при разговоре с этим API?

По аналогии, printf()Строки формата определяются в терминах этих семантических типов. Если вы хотите напечатать шрифт фиксированного размера, вам понадобятся такие макросы, какPRIu64 и т. д. в stdint.h чтобы вы могли получить правильную строку формата для печати шрифта фиксированного размера со старым printf форматировать строки.

Скорость

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

В наши дни это немного спорный ответ... это было первоначальное намерение, но из-за stdint недоступны в ранних версиях C/C++, многие платформы (например, 32-разрядная Windows или macOS X) просто гарантировали размер int а также long. Таким образом, во время перехода на 64-битные версии некоторые из этих размеров остались прежними (что привело к появлению интересных новых типов, таких какlong long, среди прочего). Вот почему мы получилиleast а также fast типы.

Переносимость кода

Семантические типы могут быть больше на 64-битной платформе, чем на 32-битной (например, чтобы индексы массива заполняли всю память). Итак, если вы работаете на разных платформах, используя семантический тип (который, по моему определению, будет включатьsize_t где доступно) вместо фиксированного означает, что вы используете лучшее оборудование и не добавляете произвольные ограничения.

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

Комментарий: не спрашивайте меня, почему они не ввели строки формата дляint64_t, int32_tи т.д. Может они закончились письмами? Может быть, слишком много баз кода определили свои собственные строки формата и сломались бы?

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