Сегодня я наткнулся на интересный момент в обзоре Code Review . В этом ответе @Veedrac рекомендовал заменить типы переменного размера (например, int
и long
) на типы фиксированного размера, такие как uint64_t
и uint32_t
. Цитата из комментариев этого ответа:
Размеры int и long (и, следовательно, значения, которые они могут содержать) зависят от платформы. С другой стороны, int32_t всегда имеет длину 32 бита. Использование int просто означает, что ваш код работает по-разному на разных платформах, что, как правило, не то, что вы хотите.
Причины, по которым стандарт не устанавливает общие типы, частично объясняются здесь @supercat. C был написан для переносимости между архитектурами, в отличие от сборки, которая обычно использовалась для системного программирования в то время.
Я думаю, изначально задумывалось, что каждый тип, кроме int, будет наименьшей вещью, которая может обрабатывать числа разных размеров, и что int будет наиболее практичным размером "общего назначения", который может обрабатывать +/- 32767.
Что касается меня, я всегда использовал int
и не очень беспокоюсь об альтернативах. Я всегда думал, что это самый шедевр с лучшими характеристиками, конец истории. Единственное место, где я думал, что фиксированная ширина будет полезна, - это кодирование данных для хранения или передачи по сети. Я редко видел типы фиксированной ширины в коде, написанном другими.
Я застрял в 70-х годах или на самом деле есть смысл использовать int
в эпоху C99 и далее?
источник
Ответы:
Существует распространенный и опасный миф о том, что такие типы, как
uint32_t
избавление программистов от необходимости беспокоиться о размереint
. Хотя было бы полезно, если бы Комитет по стандартам определил способ объявления целых чисел с машинно-независимой семантикой, неподписанные типы, такие какuint32_t
семантика, слишком свободны, чтобы позволить писать код чистым и переносимым способом; Кроме того, подписанные типы, такие какint32
имеют семантику, которая для многих приложений определена излишне жестко и, таким образом, исключает то, что в противном случае было бы полезным для оптимизации.Рассмотрим, например:
На машинах, где
int
не может храниться 4294967295 или 18446744065119617025, первая функция будет определена для всех значенийn
иexponent
, и ее поведение не будет зависеть от размераint
; кроме того, стандарт не будет требовать, чтобы он приводил к другому поведению на машинах с любым размеромint
некоторых значений,n
иexponent
, тем не менее, заставит его вызывать неопределенное поведение на машинах, где 4294967295 представляется как значение,int
а 18446744065119617025 - нет.Вторая функция выдаст Undefined Behavior для некоторых значений
n
иexponent
на машинах, гдеint
не может храниться 4611686014132420609, но даст определенное поведение для всех значенийn
иexponent
на всех машинах, где это возможно (спецификацииint32_t
подразумевают, что поведение обертки с двумя дополнениями на машинах, где она меньше чемint
).Исторически, даже если в стандарте ничего не говорилось о том, что компиляторы должны делать с
int
переполнениемupow
, компиляторы неизменно давали бы такое же поведение, как если быint
они были достаточно большими, чтобы не переполняться. К сожалению, некоторые новые компиляторы могут стремиться «оптимизировать» программы, устраняя поведение, не предусмотренное Стандартом.источник
pow
, помните, что этот код является лишь примером и не предназначен дляexponent=0
!exponent=1
, приведет к умножению n на себя один раз, так как декремент выполняется после проверки, если приращение выполняется до проверки ( т.е. --exponent), умножение не будет выполнено и само n будет возвращено.N^(2^exponent)
, но вычисления формыN^(2^exponent)
часто используются при вычислении функций возведения в степень, а возведение в степень mod-4294967296 полезно для таких вещей, как вычисление хеш-функции конкатенации двух строк, чьи хеши известны.uint32_t
на 31 никогда не даст UB, но эффективное способ вычисления 31 ^ N влечет за собой вычисления 31 ^ (2 ^ N), которые будут.int32_t
иногда определение переполнения, а иногда нет, о чем вы, вероятно, упоминаете, кажется минимальным по сравнению с тем фактом, что в первую очередь это позволяет мне рассуждать о предотвращении переполнения. И если вы хотите определенного переполнения, скорее всего, вы хотите получить результат по модулю некоторого фиксированного значения - так что вы все равно используете типы фиксированной ширины.Для значений, тесно связанных с указателями (и, следовательно, с объемом адресуемой памяти), таких как размеры буфера, индексы массивов и Windows '
lParam
, имеет смысл иметь целочисленный тип с размером, зависящим от архитектуры. Таким образом, типы переменного размера все еще полезны. Именно поэтому мы имеем определения типовsize_t
,ptrdiff_t
,intptr_t
и т.д. Они должны быть определением типов , поскольку ни один из встроенных в C целого числа типов не должно быть указатель размера.Таким образом, вопрос , действительно ли
char
,short
,int
,long
, иlong long
по - прежнему полезны.IME, для программ на C и C ++ все еще характерно использование
int
для большинства вещей. И большую часть времени (то есть, когда ваши числа находятся в диапазоне ± 32 767, и у вас нет строгих требований к производительности), это работает просто отлично.Но что, если вам нужно работать с числами в диапазоне 17-32 бит (например, в больших городах)? Вы могли бы использовать
int
, но это было бы жестким кодом зависимости платформы. Если вы хотите строго придерживаться стандарта, вы можете использоватьlong
, который гарантированно должен быть не менее 32 бит.Проблема состоит в том, что стандарт C не определяет какого-либо максимального размера для целочисленного типа. Существуют реализации, в которых
long
используется 64 бита, что удваивает использование памяти. И если этиlong
элементы окажутся элементами массива с миллионами элементов, вы поразите память как сумасшедшие.Таким образом, ни тот,
int
ниlong
другой тип не подходит для использования здесь, если вы хотите, чтобы ваша программа была кроссплатформенной и эффективной с точки зрения памяти. Введитеint_least32_t
.long
, избегая проблем усеченияint
int
, избегая потери памяти 64-разряднойlong
.int
ОТО, предположим, вам не нужны огромные числа или огромные массивы, но вам нужна скорость. И
int
может быть достаточно большим на всех платформах, но это не обязательно самый быстрый тип: 64-битные системы обычно все еще имеют 32-битныеint
. Но вы можете использоватьint_fast16_t
и получить «быстрый» тип, является ли этоint
,long
илиlong long
.Итак, есть практические варианты использования для типов из
<stdint.h>
. Стандартные целочисленные типы ничего не значат . Особенноlong
это может быть 32 или 64 бита, а может быть или не достаточно большим, чтобы содержать указатель, в зависимости от прихоти авторов компилятора.источник
uint_least32_t
заключается в том, что их взаимодействия с другими типами определены еще слабее, чем уuint32_t
. ИМХО, стандарт должен определять такие типы, какuwrap32_t
иunum32_t
, с семантикой, которую любой компилятор, определяющий типuwrap32_t
, должен продвигать как тип без знака по существу в тех же случаях, что и при продвижении, если бы онint
был 32-разрядным, и любой компилятор, который определяет тип,unum32_t
должен гарантировать, что основные арифметические продвижения всегда конвертируют его в тип со знаком, способный удерживать его значение.intN_t
иuintN_t
, и чьи определенные поведения будут согласовываться сintN_t
иuintN_t
, но которые предоставят компиляторам некоторую свободу в случае, когда код присваивает значения за пределами своего диапазона [допуская семантику, аналогичную тем, которые были возможно, предназначено дляuint_least32_t
, но без неопределенности, например, добавит ли auint_least16_t
иint32_t
a результат со знаком или со знаком.