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

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

Одна из причин, о которой я знаю, заключается в том, что при использовании символов char имеет смысл использовать char вместо использования (u)int8_t s, как упоминалось в этом вопросе.

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

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

Ответ 1

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

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

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

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

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

То же самое относится к unsigned type до 255, с unsigned char, unsigned int, uint_least8_t и uint_fast8_t.

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

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

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

Ответ 2

Одна проблема, о которой еще не упоминалось, заключается в том, что, хотя использование целочисленных типов фиксированного размера будет означать, что размеры одной переменной не изменятся, если компиляторы используют разные размеры для 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 (вычитание даст действительно большое число, которое больше пяти).

Я не знаю, сколько кода полагается на то, что промежуточные результаты выражений, включающих неподписанные типы, требуются для обертывания даже при отсутствии типов (IMHO, если поведение упаковки было желательным, программист должен был включать в себя тип) (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;
  c &= ~0x100000000;

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

Ответ 3

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

Например, в стандарте C указано, что int не менее 16-bit, а long - не менее 32-bit.

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

Ответ 4

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

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 p1, что все intN_t и uintN_t должны быть соответствующими типами с подписью и без знака. Единственная такая пара для типов символов - signed char и unsigned char.

Ответ 5

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

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