Різниця між uint8_t, uint_fast8_t та uint_least8_t


75

Стандарт C99 представляє такі типи даних. Документацію можна знайти тут для бібліотеки stdint AVR.

  • uint8_t означає, що це 8-бітний тип без підпису.
  • uint_fast8_t означає, що це найшвидший непідписаний int з принаймні 8 бітами.
  • uint_least8_t означає, що це беззнаковий int з принаймні 8 бітами.

Я розумію, uint8_tщо таке uint_fast8_t(я не знаю, як це реалізовано на рівні реєстру).

1.Чи можете ви пояснити, що означає "це unsigned intа принаймні 8 біт"?

2.Як uint_fast8_tі як uint_least8_tдопомогти збільшити ефективність / простір коду порівняно з uint8_t?


Для вашого 1-го питання я можу уявити, що, хоча uint8_tгарантовано 8 біт, uint_fast8_tгарантовано буде> = 8 біт, приблизно як unsigned char.
jacob

1
Одним з міркувань є те, uint8_tщо не існує в системах, які не мають власного 8-бітового типу. Два інших будуть там.
Піт Беккер

9
Ви отримали відповіді, які стосуються "неясних" та "екзотичних" архітектур. Ці терміни трохи упереджені. Звичайно, якщо ваш єдиний досвід роботи з настільними системами, ці архітектури виходять за рамки вашого досвіду. Але "я цього раніше не бачив" - це не те саме, що "це неясно чи екзотично". Для людей, які працюють із вбудованими системами або ЦСП, ці речі є досить поширеними.
Піт Беккер

Відповіді:


99

uint_least8_t- це найменший тип, який має щонайменше 8 біт. uint_fast8_tце найшвидший тип, який має щонайменше 8 біт.

Ви можете побачити відмінності, уявляючи собі екзотичні архітектури. Уявіть собі 20-розрядну архітектуру. Він unsigned intмає 20 бітів (один регістр) і unsigned char10 біт. Отже sizeof(int) == 2, але використання charтипів вимагає додаткових інструкцій, щоб скоротити регістри навпіл. Тоді:

  • uint8_t: не визначено (немає 8-бітного типу).
  • uint_least8_t: це unsigned charнайменший тип, що становить щонайменше 8 біт.
  • uint_fast8_t: є unsigned int, тому що в моїй уявній архітектурі змінна з напіврегістром повільніша за повну.

11
Мені подобається, як ти повинен уявляти собі екзотичні архітектури, щоб знайти для цього варіант використання. Чи знайшли вони якусь корисність на практиці?
user541686

18
@Mehrdad В ARM, наприклад, якщо ваша int_fast8_t32-розрядна змінна, вам не потрібно робити розширення знака перед арифметичними операціями.
user694733

1
Наприклад, @Mehrdad MIPS, було б дуже неправильно робити uintX_fast_tменше 32 біт. Вам навіть не потрібно уявляти архітектури, щоб uint8_tстати невизначеними, візьмемо, наприклад, UNIVAC, який є 36-бітовим, я вважаю, що існує char9-бітний.
skyking

7
@Mehrdad: Я визнаю, що я ніколи не бачив uint_leastX_tі не uint_fastX_tвикористовувався в реальних програмах. uintX_tтак, вони активно використовуються. Схоже, люди не дуже цікаві у перенесенні екзотичних архітектур. Що очікується, навіть якщо ви правильно підпишете свої підписи, ваша програма зазнає невдачі в тисячі різних речей.
rodrigo

3
@skyking: Я не кажу, що їх не слід використовувати, лише що вони не дуже використовуються на практиці. Якщо ви можете знайти реальну програму чи бібліотеку, яка їх розумно використовує, то опублікуйте посилання, оскільки я не міг знайти жодного.
rodrigo

29

uint8_t означає: дайте мені беззнаковий int рівно 8 біт.

uint_least8_tозначає: дайте мені найменший тип беззнакового int, який має щонайменше 8 біт. Оптимізуйте для споживання пам'яті.

uint_fast8_tозначає: дайте мені беззнаковий int щонайменше 8 біт. Виберіть більший тип, якщо це зробить мою програму швидшою, через міркування щодо вирівнювання. Оптимізуйте для швидкості.

Крім того, на відміну від звичайних intтипів, підписана версія вищезазначених типів stdint.h гарантовано має формат доповнення 2.


1
Дякую. Варто знати, що типи, що підписані stdint.h, гарантовано будуть доповненням двох. Цікаво, де це допоможе при написанні портативного коду.
legends2k

6
Зверніть увагу, що для використання формату доповнення 2 потрібні лише точні варіанти ширини. Також зверніть увагу, що вони не повинні існувати. Отже, платформа не повинна підтримувати формат доповнення 2.
skyking

@ legends2k: типи в stdint.hє менш корисними, ніж може сподобатися, якщо хтось намагається написати переносний код, оскільки, хоча їм потрібно використовувати формат зберігання двох доповнень, це не означає, що вони будуть демонструвати поведінку обгортання двох доповнень. Зауважте також, що навіть на платформах, де int32 біти , написання значення за допомогою int32_t*і читання за допомогою int*, або навпаки, не гарантується.
supercat

@supercat Кожен компілятор, якого я бачив, використовує внутрішній typedef для типів stdint.h, щоб зробити їх синонімами одного з основних цілочисельних типів "ключових слів". Отже, якщо ви турбуєтесь про згладжування покажчика, я не думаю, що це буде проблемою на практиці, лише теоретично.
Лундін

@Lundin: Деякі компілятори використовують "long" як typedef для int32_t, а деякі використовують "int". Навіть коли "int" і "long" мають однакове представлення, їх можна (і іноді вважати) відмінними для цілей правил накладання псевдонімів C.
supercat

26

Теорія виглядає приблизно так:

uint8_tмає бути рівно 8 біт, але не потрібно існувати. Отже, ви повинні використовувати його там, де ви покладаєтесь на поведінку присвоєння modulo-256 * 8-бітового цілого числа, і де ви віддаєте перевагу помилці компіляції та неправильній поведінці на незрозумілих архітектурах.

uint_least8_tмає бути найменшим доступним цілим числом без підпису, який може зберігати принаймні 8 бітів. Ви можете використовувати його, коли хочете мінімізувати використання пам’яті таких речей, як великі масиви.

uint_fast8_tпередбачається, що це "найшвидший" тип без підпису, який може зберігати щонайменше 8 біт; однак насправді це не гарантовано буде найшвидшим для будь-якої даної операції на будь-якому даному процесорі. Ви могли б використовувати його для обробки коду, який виконує багато операцій зі значенням.

Практика полягає в тому, що типи "швидкий" і "найменший" використовуються мало.

Типи "найменш" дійсно корисні лише в тому випадку, якщо ви дбаєте про переносимість затемнених архітектур із CHAR_BIT! = 8, чого більшість людей не робить.

Проблема "швидких" типів полягає в тому, що "найшвидший" важко визначити. Менший тип може означати менше навантаження на систему пам'яті / кеш-пам’яті, але використання типу, який менший за власний, може вимагати додаткових інструкцій. Крім того, що найкраще може змінюватися між версіями архітектури, але реалізатори часто хочуть уникнути злому ABI в таких випадках.

З огляду на деякі популярні реалізації здається, що визначення uint_fastn_t є досить довільним. glibc, схоже, визначає їх як принаймні "розмір рідного слова" розглянутої системи, не беручи до уваги той факт, що багато сучасних процесорів (особливо 64-розрядних) мають специфічну підтримку швидких операцій над елементами, меншими за рідне слово розмір. IOS, очевидно, визначає їх як еквівалентні типам фіксованого розміру. Інші платформи можуть відрізнятися.

Загалом, якщо вашою метою є виконання жорсткого коду з крихітними цілими числами, вам слід порівнювати свій код на платформах, які вам важливі, різними розмірами, щоб побачити, що найкраще працює.

* Зверніть увагу, що, на жаль, поведінка присвоєння modulo-256 не завжди передбачає арифметику modulo-256, завдяки неправильній характеристиці просування C цілого числа.


2
Визначення glibc були обрані в той час, коли цих оптимізацій не існувало, і тепер вони вписані в ABI і не можуть бути змінені. Це одна з кількох причин, чому типи _найменшого та швидкого не є фактично корисними на практиці.
zwol

4
@zwol: Я хотів би, щоб мова додавала типи типів, які були визначені з точки зору макета та семантичних вимог, наприклад "Мені потрібно щось, чиї нижчі біти будуть псевдонімами інших 16-бітових типів, і які можуть містити значення 0-65535, але я не маю йому не потрібно, щоб прив'язати більші значення до цього діапазону ". Псевдоніми, макет, діапазон та поведінка поза зоною дії повинні бути чотирма окремими аспектами типу, але C допускає лише певні комбінації, які не узгоджуються між різними платформами.
supercat

5

Деякі процесори не можуть працювати настільки ефективно з меншими типами даних, як з великими. Наприклад, враховуючи:

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

Призначеною метою "швидких" типів було дозволити компіляторам замінити менші типи, які не могли бути ефективно оброблені більш швидкими. На жаль, семантика "швидких" типів досить погано уточнена, що, у свою чергу, залишає каламутні питання щодо того, чи будуть вирази оцінюватися за допомогою підписаної чи беззнакової математики.


Додаткові потенційно непотрібні вказівки при роботі з меншим типом даних у порівнянні з більшим типом даних власного розміру слова значно допомагають проілюструвати, чому багато "швидких" типів даних можуть мати більшу бітову ширину, ніж очікувалося. Дякую за приклад.
Галактика

@Galaxy: На жаль, Стандарт не допускає можливості "найменших" типів, поведінка яких може змінюватися залежно від контексту. Наприклад, на багатьох машинах арифметика 32-розрядних значень у регістрах може бути швидшою, ніж операції з використанням 8-розрядних значень у регістрах, але 8-розрядні навантаження та збереження мають таку ж швидкість, як 32-розрядні навантаження та зберігання, і кешування проблеми можуть спричинити ефективність 8-бітових значень.
supercat

4

1.Чи можете ви пояснити, що означає "це беззнаковий int з принаймні 8 бітами"?

Це повинно бути очевидно. Це означає, що це цілий тип без підпису, і що його ширина становить щонайменше 8 біт. Фактично це означає, що він може принаймні містити числа від 0 до 255, і він точно не може містити від'ємні числа, але він може вміщувати числа, що перевищують 255.

Очевидно, що ви не повинні використовувати жоден із цих типів, якщо плануєте зберігати будь-яке число поза діапазоном від 0 до 255 (і ви хочете, щоб воно було портативним).

2.Як uint_fast8_t та uint_least8_t допомагають збільшити ефективність / простір коду порівняно з uint8_t?

uint_fast8_tпотрібно швидше, тому ви повинні це використовувати, якщо ваша вимога полягає в тому, щоб код був швидким. uint_least8_tз іншого боку, вимагає, щоб не було кандидата меншого розміру - тому ви скористаєтесь цим, якщо розмір є проблемою.


І звичайно, ви використовуєте лише uint8_tтоді, коли ви абсолютно вимагаєте, щоб це було рівно 8 біт. Використання uint8_tможе зробити код не портативним, оскільки для uint8_tцього не потрібно існувати (оскільки такий малий цілий тип не існує на певних платформах).


3

"Швидкі" цілі типи визначаються як найшвидші цілі числа з мінімум необхідною кількістю бітів (у вашому випадку 8).

Платформа може визначити, uint_fast8_tоскільки uint8_tтоді різниці в швидкості не буде абсолютно ніякої.

Причина в тому, що існують платформи, які працюють повільніше, коли не використовують довжину рідного слова.


0

Я використовую швидкі типи даних (uint_fast8_t) для локальних варів та параметрів функцій, а використання звичайних (uint8_t) у масивах та структурах, які часто використовуються, і розмір пам'яті важливіший за кілька циклів, які можна зберегти, не маючи щоб очистити або підписати розширення верхніх бітів. Чудово працює, за винятком шашок MISRA. Вони горішаться від швидких типів. Фокус у тому, що швидкі типи використовуються через похідні типи, які можна визначити по-різному для збірок MISRA та звичайних.

Я думаю, що ці типи чудово підходять для створення портативного коду, який ефективний як на мікроконтролерах низького класу, так і на процесорах великих додатків. Поліпшення може бути не величезним або зовсім незначним за умови використання хороших компіляторів, але краще, ніж нічого.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.