Разница между uint8_t, uint_fast8_t и uint_least8_t
В стандарте C99 представлены следующие типы данных. Документацию можно найти здесь для библиотеки stdint AVR.
-
uint8_t
означает 8-разрядный неподписанный тип.
-
uint_fast8_t
означает, что это самый быстрый беззнаковый int с не менее чем 8
биты.
-
uint_least8_t
означает, что он имеет unsigned int с по меньшей мере 8 битами.
Я понимаю uint8_t
и что такое uint_fast8_t
(я не знаю, как он реализован на уровне регистра).
1. Можете ли вы объяснить, что означает "это a unsigned int
с по меньшей мере 8 бит"?
2. Как uint_fast8_t
и uint_least8_t
помогают повысить эффективность/кодовое пространство по сравнению с uint8_t
?
Ответы
Ответ 1
uint_least8_t
- наименьший тип, который имеет не менее 8 бит.
uint_fast8_t
- самый быстрый тип, который имеет не менее 8 бит.
Вы можете видеть различия, представляя экзотические архитектуры. Представьте себе 20-битную архитектуру. Его unsigned int
имеет 20 бит (один регистр), а его unsigned char
имеет 10 бит. Поэтому sizeof(int) == 2
, но с использованием типов char
требуются дополнительные инструкции для сокращения регистров пополам. Тогда:
-
uint8_t
: undefined (без 8-битного типа).
-
uint_least8_t
: unsigned char
, наименьший тип, который содержит не менее 8 бит.
-
uint_fast8_t
: unsigned int
, потому что в моей воображаемой архитектуре переменная с половинным регистром медленнее, чем полная запись.
Ответ 2
Теория выглядит примерно так:
uint8_t
требуется ровно 8 бит, но он не должен существовать. Поэтому вы должны использовать его там, где вы полагаетесь на арифметическое поведение по модулю 256 для 8-битного целого и где вы предпочли бы скомпилировать отказ от неправильного поведения на неясных архитектурах.
uint_least8_t
должен быть наименьшим доступным беззнаковым целым типом, который может хранить не менее 8 бит. Вы бы использовали его, когда хотите минимизировать использование памяти такими вещами, как большие массивы.
uint_fast8_t
должен быть "самым быстрым" неподписанным типом, который может хранить не менее 8 бит; однако на самом деле он не гарантированно будет самым быстрым для любой заданной операции на любом данном процессоре. Вы использовали бы его в обработке кода, который выполняет много операций над значением.
Практика заключается в том, что "быстрый" и "наименее" типы не используются много.
"Наименее" типы действительно полезны, если вы заботитесь о переносимости, чтобы замаскировать архитектуры с помощью CHAR_BIT!= 8, которые большинство людей этого не делают.
Проблема с "быстрыми" типами заключается в том, что "самый быстрый" трудно скопировать. Меньший тип может означать меньшую нагрузку на систему памяти/кэша, но с использованием типа, который меньше, чем native, может потребоваться дополнительные инструкции. Кроме того, что лучше всего может измениться между версиями архитектуры, но разработчики часто хотят избежать нарушения ABI в таких случаях.
От взгляда на некоторые популярные реализации кажется, что определения uint_fastn_t довольно произвольны. glibc, по-видимому, определяет их как по крайней мере "размер родного слова" рассматриваемой системы, не принимая во внимание тот факт, что многие современные процессоры (особенно 64-битные) имеют определенную поддержку для быстрых операций над элементами, меньшими, чем их родное слово размер. IOS, по-видимому, определяет их как эквивалентные типам фиксированного размера. Другие платформы могут отличаться.
В целом, если ваша производительность жесткого кода с маленькими целыми числами - ваша цель, вы должны быть на месте на платформах, которые вам нужны, с разными типами, чтобы увидеть, что лучше всего работает.
Ответ 3
uint8_t
означает: дать мне unsigned int ровно 8 бит.
uint_least8_t
означает: дайте мне наименьший тип unsigned int, который имеет не менее 8 бит. Оптимизируйте потребление памяти.
uint_fast8_t
означает: дать мне unsigned int не менее 8 бит. Выберите более крупный тип, если он ускорит мою программу из-за соображений выравнивания. Оптимизируйте скорость.
Кроме того, в отличие от простых типов int
, подписанная версия указанных выше типов stdint.h гарантированно является 2-мя дополнительными форматами.
Ответ 4
1. Можете ли вы объяснить, что означает "это неподписанный int с не менее 8 бит"?
Это должно быть очевидно. Это означает, что это целочисленный тип без знака и ширина его не менее 8 бит. Фактически это означает, что он может по крайней мере удерживать числа от 0 до 255, и он может определенно не содержать отрицательные числа, но он может удерживать числа выше 255.
Очевидно, что вы не должны использовать какой-либо из этих типов, если вы планируете хранить любое число вне диапазона от 0 до 255 (и вы хотите, чтобы оно было переносимым).
2.How uint_fast8_t и uint_least8_t помогают повысить эффективность/кодовое пространство по сравнению с uint8_t?
uint_fast8_t
требуется быстрее, поэтому вы должны использовать это, если требуется, чтобы код был быстрым. uint_least8_t
, с другой стороны, требуется, чтобы не было кандидата меньшего размера, поэтому вы должны использовать это, если размер является проблемой.
И, конечно, вы используете только uint8_t
, когда вы абсолютно требуете, чтобы это было ровно 8 бит. Использование uint8_t
может сделать код не переносимым, поскольку uint8_t
не требуется, чтобы существовать (потому что такой маленький целочисленный тип не существует на некоторых платформах).
Ответ 5
"Быстрые" целые типы определяются как самое быстрое целое число, доступное, по крайней мере, с количеством бит (в вашем случае 8).
Платформа может определять uint_fast8_t
как uint8_t
, тогда не будет никакой разницы в скорости.
Причина в том, что есть платформы, которые медленнее, если не используют их родную длину слова.
Ответ 6
Некоторые процессоры не могут работать так же эффективно на небольших типах данных, как на больших. Например, данный:
uint32_t foo(uint32_t x, uint8_t y)
{
x+=y;
y+=2;
x+=y;
y+=4;
x+=y;
y+=6;
x+=y;
return x;
}
если y
были uint32_t
, компилятор для ARM Cortex-M3 мог просто генерировать
add r0,r0,r1,asl #2 ; x+=(y<<2)
add r0,r0,#12 ; x+=12
bx lr ; return x
но поскольку y
есть uint8_t
, компилятор должен будет генерировать:
add r0,r0,r1 ; x+=y
add r1,r1,#2 ; Compute y+2
and r1,r1,#255 ; y=(y+2) & 255
add r0,r0,r1 ; x+=y
add r1,r1,#4 ; Compute y+4
and r1,r1,#255 ; y=(y+4) & 255
add r0,r0,r1 ; x+=y
add r1,r1,#6 ; Compute y+6
and r1,r1,#255 ; y=(y+6) & 255
add r0,r0,r1 ; x+=y
bx lr ; return x
Предполагаемая цель "быстрых" типов заключалась в том, чтобы позволить компиляторам заменять более мелкие типы, которые не могли быть эффективно обработаны с более быстрыми. К сожалению, семантика "быстрых" типов довольно плохо указана, что, в свою очередь, оставляет мрачные вопросы о том, будут ли выражения оцениваться с использованием подписанной или неподписанной математики.