Чи були замінені типи змінної ширини фіксованими типами в сучасному C?


21

Сьогодні я натрапив на цікавий момент в огляді на Code Review . У цій відповіді @Veedrac рекомендує замінити типи змінних розмірів (наприклад, intта long) на типи фіксованого розміру, такі як uint64_tі uint32_t. Цитування з коментарів цієї відповіді:

Розміри int та long (і, отже, значення, які вони можуть утримувати) залежать від платформи. З іншого боку, int32_t завжди має 32 біти. Використання int просто означає, що ваш код працює по-різному на різних платформах, що, як правило, не те, що ви хочете.

Міркування за стандартом, що не фіксує загальні типи, частково пояснюється тут @supercat. С було написано, що воно переноситься в архітектурах, на відміну від складання, яке зазвичай використовувалося для системного програмування в той час.

Я думаю, що проектний задум був спочатку, щоб кожен тип, крім int, був найменшою річчю, яка може обробляти номери різних розмірів, і що int є найпрактичнішим розміром «загального призначення», який міг би працювати з +/- 32767.

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

Я застряг у 70-х чи насправді є обґрунтуванням використання intв епоху С99 та поза нею?


1
Частина людей просто імітує інших. Я вважаю, що більшість кодів з фіксованим бітом було зроблено сумлінно. Немає причин встановлювати розмір ні на ні. У мене є код, створений в основному на 16-бітних платформах (MS-DOS і Xenix 80-х років), які просто компілюють і запускають сьогодні на будь-яких 64 і переваги нового розміру слова та адреси, просто компілюючи його. Тобто, серіалізація для експорту / імпорту даних є дуже важливою архітектурною конструкцією для збереження її портативної.
Лучано

Відповіді:


7

Існує поширений і небезпечний міф, що такі типи, як uint32_tрятують програмістів, не турбуються про розмір int. Хоча було б корисно, якби Комітет стандартів визначив засіб декларування цілих чисел з незалежною від машинної семантики, типи, що не uint32_tмають підпису, на зразок мають занадто вільну семантику, щоб дозволяти писати код таким чином, як чистим і портативним; крім того, підписані типи, як-от, int32мають семантику, яка для багатьох застосувань визначена непотрібно чітко і, таким чином, виключає те, що інакше було б корисним для оптимізації.

Розглянемо, наприклад:

uint32_t upow(uint32_t n, uint32_t exponent)
{
  while(exponent--)
    n*=n;
  return n;
}

int32_t spow(int32_t n, uint32_t exponent)
{
  while(exponent--)
    n*=n;
  return n;
}

На машинах, де intабо не вміщено 4294967295, або можуть утримуватися 18446744065119617025, перша функція буде визначена для всіх значень nта exponent, і на її поведінку не вплине розмір int; Крім того, цей стандарт не вимагає, щоб він спричинив різну поведінку на машинах з будь-яким розміром int деяких значень nі exponent, однак, призведе до того, щоб викликати не визначене поведінку на машинах, де 4294967295 є представним як, intале 18446744065119617025 не є.

Друга функція призведе до не визначеної поведінки для деяких значень nта exponentна машинах, де intне можна вмістити 4611686014132420609, але спричинить визначене поведінку для всіх значень nта exponentдля всіх машин, де це можливо (технічні умови int32_tозначають, що поведінка з обгортанням двох доповнень на машинах, де це менше, ніж int).

Історично, навіть якщо Standard нічого не говорив про те, що компілятори повинні робити із intпереповненням upow, компілятори послідовно матимуть таку саму поведінку, як ніби intбули достатньо великими, щоб не переповнювати. На жаль, деякі нові компілятори можуть прагнути "оптимізувати" програми, усуваючи поведінку, яка не передбачена стандартом.


3
Усі, хто захоче вручну реалізувати pow, пам’ятайте, що цей код є лише прикладом і не задовольняє exponent=0!
Марк Херд

1
Я думаю, вам слід використовувати оператор скорочення префікса, а не постфікс, в даний час він робить 1 додаткове множення, наприклад, exponent=1це призведе до того, що n буде множено на себе один раз, оскільки декремент виконується після перевірки, якщо приріст виконується до перевірки ( тобто - експонент), ніякого множення не буде виконуватися і сам n буде повернуто.
ALXGTV

2
@MarkHurd: Функція названа погано, оскільки те, що вона насправді обчислює N^(2^exponent), але обчислення форми N^(2^exponent)часто використовуються для обчислення функцій експоненціації, а мод-4294967296 експоненція корисна для таких речей, як обчислення хесу конкатенації двох рядків, чиї хеші відомі.
supercat

1
@ALXGTV: Ця функція повинна була бути ілюстративною для чогось, що обчислює щось, що стосується влади. Насправді він обчислює N ^ (2 ^ показник), який є частиною ефективного обчислення N ^ -показника, і цілком може вийти з ладу, навіть якщо N малий (повторне множення uint32_tна 31 ніколи не дасть UB, але ефективне спосіб обчислення 31 ^ N тягне за собою обчислення 31 ^ (2 ^ N), що буде.
supercat

Я не думаю, що це гарний аргумент. Метою не є визначення функцій, визначених для всіх входів, чутливими чи ні; це вміти міркувати про розміри та переповнення. int32_tіноді визначивши переповнення, а іноді ні, про що ви, мабуть, згадуєте, видається мінімально важливим щодо того, що це дозволяє мені в першу чергу міркувати про запобігання переповнення. І якщо ви хочете, щоб було визначене переповнення, швидше за все, ви хочете, щоб результат модуля отримав деяку фіксовану величину - тому ви все одно використовуєте типи фіксованої ширини.
Ведрак

4

Для значень, тісно пов'язаних з покажчиками (а значить, і з кількістю адресної пам'яті), такими як розміри буфера, індекси масиву та 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.

  • Ваш компілятор I16L32 надає 32-розрядні long, уникаючи проблем з усіченнямint
  • Ваш компілятор I32L64 надає 32-розрядні int, уникаючи марної пам'яті 64-бітного long.
  • Ваш компілятор I36L72 надає вам 36-біт int

OTOH, припустимо, вам не потрібні величезні числа чи величезні масиви, але у вас є потреба у швидкості. І intможе бути достатньо великим на всіх платформах, але це не обов'язково найшвидший тип: 64-бітні системи зазвичай все ще мають 32-розрядні int. Але ви можете використовувати int_fast16_tі отримати «швидкий» тип, є чи це int, longчи long long.

Отже, є випадки практичного використання для типів з <stdint.h>. Стандартні цілі типи нічого не означають . Зокрема long, це може бути 32 або 64 біта, а може бути і не бути достатньо великим, щоб утримувати покажчик, залежно від примхи авторів-компіляторів.


Проблема таких типів uint_least32_tполягає в тому, що їх взаємодія з іншими типами ще слабкіше визначена, ніж у тих uint32_t. IMHO, Стандарт повинен визначати такі типи, uwrap32_tі unum32_tз семантикою, що будь-який компілятор, який визначає тип uwrap32_t, повинен просувати як неподписаний тип, по суті в тих самих випадках, як це було б рекламовано, якби intбуло 32 біти, і будь-який компілятор, який визначає тип, unum32_tповинен гарантувати, що основні арифметичні акції завжди перетворюють його на підписаний тип, здатний утримувати його значення.
supercat

Крім того, стандарт може також визначити типи, зберігання і накладення спектрів були сумісні з intN_tі uintN_t, і чиїм певними моделями поведінка буде відповідати з intN_tі uintN_t, але дасть укладачам деяку свободу в разі коді , присвоєне значення за межами діапазону [дозволяючи семантику , аналогічне до тих , які були можливо, призначений для uint_least32_t, але без невизначеностей, таких як додавання a uint_least16_tі a int32_tдасть підписаний чи зменшений результат.
supercat
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.