типи даних фіксованої довжини в C / C ++


74

Я чув, що розмір типів даних, таких як, intможе відрізнятися залежно від платформи.

Моє перше запитання: чи може хтось навести якийсь приклад, що йде не так, коли програма припускає, що intце 4 байти, але на іншій платформі це кажуть 2 байти?

Ще одне питання, яке у мене виникло, пов’язане. Я знаю , що люди вирішують цю проблему з деякими typedefs, як у вас є такі змінні , як u8, u16, u32- які гарантовано будуть 8bits, 16 - бітові, 32біт, незалежно від платформи - моє запитання, як це досягається , як правило? (Я не маю на увазі типи з stdintбібліотеки - мені цікаво вручну, як можна домогтися того, щоб якийсь тип завжди говорив 32 біти незалежно від платформи ??)

c++  c 

3
Існують потенційні проблеми із перезаписом пам’яті. Якщо ви припускаєте, що ціле число дорівнює 4 байтам, коли це 2 байти на іншій платформі, залежно від того, як розміщена пам’ять, ви можете замінити наступні 2 байти після цілого числа.
Остін Бранкхорст,

9
гарний час прочитати (старі, але все ще надзвичайно інформативні) C-відповіді на поширені запитання з днів Usenet: faqs.org/faqs/C-faq/abridged, а потім faqs.org/faqs/C-faq/faq (не скорочені, тож якщо ви можу прочитати це замість цього! ще багато інформації). У ньому йдеться про багато з них, а також про багато інших часто неправильних припущень (внутрішнє представлення NULL тощо). (Обов’язково прочитайте розділ про нульові значення та про вказівники / масиви. Решта теж ДОБРА, і відкриває очі на багато предметів)
Олів’є Дюлак,

1
Зверніть увагу, що порядок байтів може також відрізнятися залежно від платформи. (+1 для запитання - краще задати запитання, тоді припустити, "безумовно, так sizeof(void *)буде завжди 4).
Мацей П'єхотка

@MaciejPiechotka: погодився. і це добре розмістити, оскільки багато читачів змогли б тоді усвідомити потенційну підводну камеру та її рішення! Немає поганих питань [ну, якщо вони дають достатньо контексту], просто погані відповіді ^^
Олів'є Дюлак,

Відповіді:


41

Я знаю, що люди вирішують цю проблему з деякими typedefs, наприклад, у вас є такі змінні, як u8, u16, u32 - які гарантовано становлять 8 біт, 16 біт, 32 біт, незалежно від платформи

Є деякі платформи, які не мають типів певного розміру (наприклад, наприклад, 28xxx TI, де розмір символу становить 16 біт). У таких випадках неможливо мати 8-розрядний тип (якщо ви цього не дуже хочете, але це може спричинити показник продуктивності).

як цього зазвичай досягають?

Зазвичай з typedefs. c99 (і c ++ 11) мають ці typedefs у заголовку . Отже, просто використовуйте їх.

хтось може навести якийсь приклад, що йде не так, коли програма припускає, що int становить 4 байти, але на іншій платформі це кажуть 2 байти?

Найкращий приклад - це зв'язок між системами різного розміру. Надсилаючи масив ints з однієї на іншу платформу, де sizeof (int) різниться удвох, потрібно бути гранично обережним.

Крім того, збереження масиву ints у двійковому файлі на 32-бітній платформі та переосмислення його на 64-бітній платформі.


14
+1 для збереження масиву ints у двійковому файлі на 32-бітній платформі та переосмислення його на 64-бітній платформі. .
legends2k

22

У попередніх ітераціях стандарту C ви зазвичай робили власні typedefзаяви, щоб переконатися, що отримали (наприклад) 16-розрядний тип на основі #defineрядків, переданих у компілятор, наприклад:

gcc -DINT16_IS_LONG ...

У наш час (C99 і вище) існують конкретні типи, такі як uint16_tціле 16-бітове беззнакове ціле число.

За умови включення stdint.hви отримуєте точні типи ширини бітів, типи принаймні такої ширини, найшвидші типи із заданим мінімальним розміром і так далі, як це задокументовано вC99 7.18 Integer types <stdint.h> . Якщо реалізація має сумісні типи, вони повинні їх надати.

Також дуже корисним є inttypes.hдодавання деяких інших приємних функцій для перетворення формату цих нових типів ( printfі scanfрядків форматування).


1
Підзапитання: Якщо платформа не підтримує 16-розрядний цілочисельний тип, unint16_tне визначено в cstdintetc ..? Або стандартна гарантія, що тип завжди буде там (і робити речі всередині, щоб переконатися, що він працює)?
Мартін Йорк,

5
Ні, стандарт C вимагає цього, лише якщо реалізація має сумісний тип. Наприклад, якщо ви працюєте на 12-розрядному DSP, він не повинен надавати 16-розрядний uint16_t. Це може, але це не обов'язково:7.18.1.1/3: These types are optional. However, if an implementation provides integer types with widths of 8, 16, 32, or 64 bits, no padding bits, and (for the signed types) that have a two’s complement representation, it shall define the corresponding typedef names.
paxdiablo

4
Отже, якщо ви використовуєте uint16_tі платформа не підтримує його, тоді ми можемо очікувати помилки компіляції під час процесів перенесення.
Мартін Йорк,

1
@Loki, так, компілятор не знатиме типу.
paxdiablo

16

По першому питанню: Ціле переповнення .

Для другого питання: наприклад, до typedefцілого числа без підпису на 32 біти, на платформі, де intдорівнює 4 байти, використовуйте:

 typedef unsigned int u32;

На платформі, де int2 байти, а long4 байти:

typedef unsigned long u32;

Таким чином, вам потрібно змінити лише один файл заголовка, щоб зробити типи крос-платформними.

Якщо є деякі макроси для певної платформи, цього можна досягти, не змінюючи вручну:

#if defined(PLAT1)
typedef unsigned int u32;
#elif defined(PLAT2)
typedef unsigned long u32;
#endif

Якщо stdint.hпідтримується C99 , це переважно.


Неважливо, бувають такі часи ... - відпочиньте!
alk

Що тут платформа? Це апаратне забезпечення - наприклад, x86, x86_64, AMD тощо ... чи це операційна система - як Solaris, AIX, HP-UX, Linux, macOS, BSD та IBM z / OS тощо ...?
Darshan L

8

Перш за все: Ніколи не пишіть програми, які покладаються на ширину типів типу short,int ,unsigned int , ....

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

Якщо ви хочете бути по-справжньому незалежним від платформи і зберігати, наприклад, значення 33000 як підписане ціле число, ви не можете просто припустити, що його intбуде утримувати його. Ан intмає принаймні діапазон -32767до 32767або -32768до 32767(залежно від доповнення одиниць / двох). Цього просто недостатньо, хоча зазвичай це 32 біти і, отже, здатне зберігати 33000. Для цього значення вам остаточно потрібен >16bitтип, отже, ви просто вибираєте int32_tабо int64_t. Якщо цього типу не існує, компілятор повідомить вам про помилку, але це не буде тихою помилкою.

По-друге: C ++ 11 забезпечує стандартний заголовок для цілочисельних типів фіксованої ширини. Жодне з них не гарантується на вашій платформі, але коли вони існують, вони гарантовано мають точну ширину. Див. Цю статтю на cppreference.com для довідки. Типи іменуються в форматі int[n]_tі uint[n]_tде nце 8, 16, 32або 64. Вам потрібно буде включити заголовок <cstdint>. CТема звичайно <stdint.h>.


2
OP: " Я не маю на увазі типи з бібліотеки stdint - мені цікаво вручну, як можна домогтися того, щоб якийсь тип завжди був 32-бітовим, незалежно від платформи ??) ";
legends2k

2
@ legends2k Правильним способом встановлення цілих типів фіксованої ширини є використання стандартних бібліотек.
stefan

4
Погоджено, але саме тоді ви пишете код, а не коли намагаєтесь дізнатися, як такі заголовки пишуться в першу чергу.
legends2k

7
" Перш за все: ніколи не пишіть програми, які покладаються на ширину типів ". Отже, ви говорите, що ми не повинні покладатися на uint32_tширину 32 біти? Абстракції приємні, і все, але з часом настає момент, коли вам потрібно зробити деякі припущення, щоб насправді щось зробити.
Томас

6
Що ви маєте на увазі, "ніколи не пишіть програми, які покладаються на ширину типів"? Ширина типів безпосередньо впливає на діапазон можливих значень, і це дуже важливо при виборі, які типи використовувати, особливо для тих типів завдань програмування, для яких багато людей використовують C / C ++. Якщо ви пишете файлову систему або щось, що потребує зберігання великої кількості значень у обмеженій пам’яті, вам потрібно приймати такі рішення. Існує причина, що рядки не зберігаються як масиви без підпису long long.
tfinniga

6

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

У першому сценарії:

int x = 32000;
int y = 32000;
int z = x+y;        // can cause overflow for 2 bytes, but not 4

У другому сценарії

struct header {
int magic;
int w;
int h;
};

то переходить до fwrite:

header h;
// fill in h
fwrite(&h, sizeof(h), 1, fp);

// this is all fine and good until one freads from an architecture with a different int size

У третьому сценарії:

int* x = new int[100];
char* buff = (char*)x;


// now try to change the 3rd element of x via buff assuming int size of 2
*((int*)(buff+2*2)) = 100;

// (of course, it's easy to fix this with sizeof(int))

Якщо ви використовуєте відносно новий компілятор, я б використовував uint8_t, int8_t тощо, щоб бути впевненим у розмірі типу.

У старих компіляторах typedef зазвичай визначається для кожної платформи. Наприклад, можна зробити:

 #ifdef _WIN32
      typedef unsigned char uint8_t;
      typedef unsigned short uint16_t;
      // and so on...
 #endif

Таким чином, для кожної платформи міститься заголовок, який визначає особливості цієї платформи.


2
+1 за те, що першим згадав конструкції. Ви також повинні знати, що відбувається, коли ви надсилаєте повідомлення через мережу.
Джеймс Андерсон,

5

Мені цікаво вручну, як можна домогтися того, що якийсь тип - це завжди скажімо 32 біти незалежно від платформи ??

Якщо ви хочете, щоб компіляція вашої (сучасної) програми C ++ зазнала невдачі, якщо заданий тип не відповідає вашій ширині, додайте static_assertдесь. Я б додав це навколо того місця, де робляться припущення щодо ширини типу.

static_assert(sizeof(int) == 4, "Expected int to be four chars wide but it was not.");

chars на найбільш часто використовуваних платформах мають розмір 8 біт, але не всі платформи працюють таким чином.


3
sizeofфактично повертає розмір у charс, а не байт. Отже, якщо ви хочете перевірити розмір у бітах , вам слід це зробити sizeof(int) * CHAR_BIT == 32.
user694733

static_assert доступний лише за останнім стандартом. Але uint_32t та подібні типи доступні раніше
Сем

@ user694733 Ні. Розмір у символах = розмір у байтах, за визначенням. sizeof(char)==1- завжди.
Конрад Рудольф

@sammy Nope uint32_tтощо були додані одночасно з static_assert.
Конрад Рудольф

@KonradRudolph Це залежить від визначення байта. Зазвичай байт вважається 8 бітами. charзавжди має CHAR_BITбіти. CHAR_BITстановить принаймні 8, але може бути і більше.
user694733

3

Ну, перший приклад - приблизно такий:

int a = 45000; // both a and b 
int b = 40000; // does not fit in 2 bytes.
int c = a + b; // overflows on 16bits, but not on 32bits

Якщо ви подивіться на cstdintзаголовок, ви побачите , як всі типи фіксованого розміру ( int8_t, uint8_tі т.д.) визначено - і єдине , що відрізняється між різними архітектурами цей заголовки. Отже, на одній архітектурі int16_tможе бути:

 typedef int int16_t;

і на іншому:

 typedef short int16_t;

Крім того, є й інші типи, які можуть бути корисними, наприклад: int_least16_t


2
  1. Якщо тип менше, ніж ви думаєте, він, можливо, не зможе зберегти значення, яке вам потрібно зберегти в ньому.
  2. Для створення типів фіксованого розміру ви читаєте документацію для підтримуваних платформ, а потім визначаєте typedefs на основі #ifdefдля конкретних платформ.

2

хтось може навести якийсь приклад, що йде не так, коли програма припускає, що int становить 4 байти, але на іншій платформі це кажуть 2 байти?

Скажімо, ви розробили програму для зчитування 100 000 входів, і підраховуєте її, використовуючи unsigned intприпустимо розмір 32 біти (32-бітові непідписані вклади можуть рахувати до 4 294 967 295). Якщо ви скомпілюєте код на платформі (або компіляторі) з 16-розрядними цілими числами (16-розрядні непідписані ints можуть рахувати лише до 65 535), значення обернеться після 65535 через ємність і позначає неправильний підрахунок.


1

Укладачі відповідають за дотримання стандарту. Коли ви включаєте <cstdint>або<stdint.h> вони надають типи відповідно до стандартного розміру.

Компілятори знають, що складають код для якої платформи, тоді вони можуть генерувати деякі внутрішні макроси або магії для створення відповідного типу. Наприклад, компілятор на 32-бітній машині генерує __32BIT__макрос, і раніше він має такі рядки у stdintфайлі заголовка:

#ifdef __32BIT__
typedef __int32_internal__ int32_t;
typedef __int64_internal__ int64_t;
...
#endif

і ви можете ним скористатися.


0

бітові прапори є тривіальним прикладом. 0x10000 викличе у вас проблеми, ви не можете маскуватись ним або перевіряти, чи встановлений біт у цій 17-й позиції, якщо все обрізано або розбито, щоб вміститися в 16-бітові.

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