В уроці про цілочисельні типи даних ми говорили, що C++ гарантує тільки їх мінімальний розмір, вони ж можуть займати і більше (в залежності від компілятора і/або архітектури комп’ютера).
Чому розмір всіх цілочисельних типів не фіксований?
Якщо говорити в загальному, то все почалося ще з мови Cі, коли продуктивність мала першочергове значення. У мові Cі навмисно залишили розмір цілочисельних типів не фіксованим для того, щоб компілятор міг самостійно підібрати найбільш підходящий розмір для певного типу даних в залежності від комп’ютерної архітектури.
Цілочисельні типи фіксованого розміру
Щоб вирішити питання кросплатформенності, в С++ додали набір цілочисельних типів фіксованого розміру, які гарантовано мають один і той же розмір на будь-якій архітектурі:
Назва | Тип | Діапазон значень |
int8_t | 1 байт signed | від -128 до 127 |
uint8_t | 1 байт unsigned | від 0 до 255 |
int16_t | 2 байта signed | від -32 768 до 32 767 |
uint16_t | 2 байта unsigned | від 0 до 65 535 |
int32_t | 4 байта signed | від -2 147 483 648 до 2 147 483 647 |
uint32_t | 4 байта unsigned | від 0 до 4 294 967 295 |
int64_t | 8 байт signed | від -9 223 372 036 854 775 808 до 9 223 372 036 854 775 807 |
uint64_t | 8 байт unsigned | від 0 до 18 446 744 073 709 551 615 |
Починаючи з C++11 доступ до цих типів здійснюється через підключення заголовку cstdint (знаходяться ці типи даних в просторі імен std). Розглянемо приклад на практиці:
1 2 3 4 5 6 7 8 9 |
#include <iostream> #include <cstdint> int main() { std::int16_t i(5); // пряма ініціалізація std::cout << i << std::endl; return 0; } |
Оскільки цілочисельні типи фіксованого розміру були додані ще до C++11, то деякі старі компілятори надають доступ до них через підключення заголовку stdint.h.
Якщо ваш компілятор не підтримує cstdint чи stdint.h, то ви можете завантажити кросплатформний заголовковий файл pstdint.h. Просто підключіть його до вашого проекту, і він самостійно визначить цілочисельні типи фіксованого розміру для вашої системи/архітектури.
Попередження щодо std::int8_t і std::uint8_t
З певних причин в C++ більшість компіляторів визначають і опрацьовують типи int8_t
і uint8_t
ідентично типам char signed
і char unsigned
, але це відбувається далеко не у всіх випадках. Отже, std::cin і std::cout можуть працювати не так, як ви очікуєте. Наприклад:
1 2 3 4 5 6 7 8 9 10 |
#include <iostream> #include <cstdint> int main() { std::int8_t myint = 65; std::cout << myint << std::endl; return 0; } |
На більшості архітектур результат виконання цієї програми наступний:
A
Тобто програма вище опрацьовує myint
як змінну типу char. Однак на деяких комп’ютерах результат може бути наступним:
65
Тому ідеальним варіантом буде уникати використання std::int8_t і std::uint8_t взагалі (використовуйте замість них std::int16_t або std::uint16_t). Однак, якщо ви використовуєте std::int8_t або std::uint8_t, то ви повинні бути обережні з будь-якою функцією, яка може інтерпретувати std::int8_t або std::uint8_t в символьний тип даних, замість цілочисельного (наприклад, з об’єктами std::cin і std::cout).
Правило: Уникайте використання std::int8_t і std::uint8_t. Якщо ви використовуєте ці типи, то будьте уважні, так як в деяких випадках вони можуть сприйматися як тип char.
Недоліки цілочисельних типів фіксованого розміру
Цілочисельні типи фіксованого розміру можуть не підтримуватися на певних архітектурах (де вони не мають можливості бути представлені). Також ці типи можуть бути менш продуктивними, ніж фундаментальні типи даних, на певних архітектурах.
Суперечка щодо unsigned
Багато розробників (і навіть великі організації) вважають, що програмісти повинні уникати використання цілочисельних типів unsigned взагалі. Головна причина — це непередбачувана поведінка і результати, які можуть виникнути при “змішуванні” цілочисельних типів signed і unsigned в програмі.
Розглянемо наступний фрагмент коду:
1 2 3 4 5 6 7 8 9 |
void doSomething(unsigned int x) { // Виконання деякого коду x разів } int main() { doSomething(-1); } |
Що станеться в цьому випадку? -1
перетвориться в інше велике число (швидше за все в 4 294 967 295
). Але горе полягає в тому, що запобігти цьому ми не можемо. C++ вільно конвертує числа з типів unsigned в типи signed і навпаки без перевірки діапазону допустимих значень певного типу даних. А це, в свою чергу, може привести до переповнення.
Б’ярн Страуструп, творець C++, казав: “Використовувати тип unsigned (замість signed) для отримання ще одного біта для представлення додатних цілих чисел, майже ніколи не є хорошою ідеєю”.
Це не означає, що ви повинні уникати використання типів unsigned взагалі. Ні! Але якщо ви їх використовуєте, то використовуйте тільки там, де це дійсно має сенс, а також подбайте про те, щоб не допустити “змішування” типів unsigned з типами signed (як в прикладі вище).