Урок №32. Фиксированный размер целочисленных типов данных

  Юрий  | 

  |

  Обновл. 11 Сен 2021  | 

 128280

 ǀ   26 

На уроке о целочисленных типах данных мы говорили, что 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). Рассмотрим пример на практике:

Поскольку целочисленные типы фиксированного размера были добавлены еще до 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 могут работать не так, как вы ожидаете. Например:

На большинстве компьютеров с различными архитектурами результат выполнения этой программы следующий:

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 преобразуется в другое большое число (скорее всего в 4 294 967 295). Но самое грустное в этом то, что предотвратить это мы не сможем. Язык C++ свободно конвертирует числа с типами unsigned в типы signed и наоборот без проверки диапазона допустимых значений определенного типа данных. А это, в свою очередь, может привести к переполнению.

Бьёрн Страуструп, создатель языка C++, считает, что: «Использовать тип unsigned (вместо signed) для получения еще одного бита для представления положительных целых чисел, почти никогда не является хорошей идеей».

Это не означает, что вы должны избегать использования типов unsigned вообще — нет. Но если вы их используете, то используйте только там, где это действительно имеет смысл, а также позаботьтесь о том, чтобы не допустить «смешивания» типов unsigned с типами signed (как в вышеприведенном примере).

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (782 оценок, среднее: 4,95 из 5)
Загрузка...

Комментариев: 26

  1. Виктор Климец:

    … еще  по поводу  int8_t.    Задал     значение    переменной  65 . Попробовал  отнять от  такой переменной ноль — получилось    численное  значение переменной     65 .  После этого ожидал, что  в памяти  переменная  станет числовой — увы,  осталась  символьной   А.  ( VS 2022).   Поэтому я  вижу   решение   этой проблемы, если   нужен  именно  тип   с   таким диапазоном переменных , в   использовании    int8_t только в   связке с  числовыми  данными  , например:    std::uint8_t myint = 173;
    std::cout << myint-0 << std::endl;
    std::cout << myint + myint << std::endl;

    Как вариант использования этой переменной  — легкий переход  от  символьного к числовому значению переменной — как говорится — только  добавь (воды) ноль..

    Для себя  я определил, что  буду держаться  подальше от таких подарков…,

  2. Анатолий:

    Если вычесть 0 (ноль) из переменной с типом данных char, или как в нашем случае из int8_t, то на выходе получим не символ а необходимое нам число.

    1. Kuchizuke:

      Как это работает?

      1. Kuchizuke:

        На выходе получается 65. Это какое-то неявное преобразование?

      2. Андрей:

        Как я понял, переменная типа uint8_t остается числом, однако, когда мы используем её в функции cout, она интерпретируется как символ ASCII, соответствующий этому числу. Однако, если мы производим арифметическую операцию, отнимая 0, то эта переменная уже интерпретируется как число

  3. Vitalt1158:

    Привет тебе путник с урока 117, ты сюда зайдешь скорее всего, чтобы вспомнить тему данного урока;)

  4. Алексей:

    >> "По определенным причинам в C++ большинство компиляторов определяют и обрабатывают типы int8_t и uint8_t идентично типам char signed и char unsigned"

    Язык со строгой типизацией, кстати…

    1. Бебра:

      С нестрогой, но в то же время со статической. Нужно отличать понятия строгая/нестрогая типизация с динамической/статической.

  5. Andrew:

    К главке "Спор насчет unsigned"

    Другой вариант иллюстрации. Может кому-нибудь облегчит понимание.

  6. Дмитрий:

    Юрий, а почему в этой программе выводится "А"?
    Я что-то где-то упустил?

    1. Лиза Су:

      Да, упустил. Прочитайте
      'Предупреждение насчёт std::int8_t и std::uint8_t'
      Думаю, поздно ответил

    2. Kuchizuke:

      Сопоставьте тип char с таблицей ASCII

  7. A-Lex:

    1. Sasha:

      Очень странно

    2. Sasha:

      Понял из-за чего могла возникнуть ошибка. Функция pow принимает в качестве аргументов числа типа double, а как мы знаем с ними могут быть ошибки округления. И компилятор code::blocks и visual studio обрабатывают это по-разному. Если написать свою функцию возведения в степень для целых чисел, все ок

    3. Александр:

      Зачем запихивать пользовательский ввод и возведение в степень с последующим возвратом, в отдельную функцию. А потом эту функцию впихивать в присвоение между математическими операторами ? Задача ведь намного проще стоит.

      Все что нужно это:

  8. AleksTs:

    Спасибо за вашу проделанную работу!!! Все cool !

    1. Фото аватара Юрий:

      Пожалуйста))

  9. zashiki:

    Подскажите!
    Использую VS express 2017

    Если unsigned переменной, к примеру, для 16-битного short, присвоить отрицательное число, то программа изменит на соответствующее unsigned положительное, т.е. -1 конвертируется в 65535. И тут все понятно.

    Но если signed переменной, к примеру, для 16-битного short, присвоить число больше предела signed, к примеру 65535, то оно не конвертируется в -1, а просто конвертируется в предел 32767, и так с любым больше предела!

    Это какая то машинная особенность или просто особенность VS?

    1. zashiki:

      прошу прощения, проблема, оказывается, только тогда, если переменная присваивается пользователем, т.е. через cin

      Но все равно вопрос остается.

    2. Дмитрий:

      Это машинная особенность.
      К примеру 15 в двоичном — 1111
      Тогда как 16 в двоичном 10000
      Но все компиляторы прочитают только последние 4 розряда и на число 16 выдадут 0.
      Вам будет полезно нагуглить арифметические действия в двоичном коде, а так же прямой, обратный и дополнительный код 😉

  10. Алексей:

    Здравствуйте Юрий, у меня раньше (чуть меньше половины года) спокойно работал Русский язык. Сейчас же он выводит вот это: Яырслш Ярф ШчырсшЫ… и т. д. Подскажите решение, пожалуйста.

  11. David:

    Юрий, можно вас спросить, можно ли восстановить потерянные знания по с++?

    1. Фото аватара Юрий:

      Можно, если приложить соответствующих усилий.

  12. Константин:

    Юра, а как я пойму, что передо мной чума, в смысле собственная версия компилятора фикс цел числ типа?

  13. Алексей:

    По поводу фиксированные типов, наткнулся тут на статейку:
    "Процессоры с 36-битной архитектурой как правило имеют 9-битный байт, а в некоторых DSP от Texas Instruments байты состоят из 16 или 32 бит. Древние архитектуры могут иметь короткие байты из 4, 5 или 7 бит."
    И для таких систем приведенные типы не будут работать, так как в них не существует int8_t = 8 байтам.
    Поэтому надежнее использовать:
    int_leastN_t (int_least8_t … int_least64_t; uint_least8_t … uint_least64_t) — минимальный целочисленный тип шириной не менее N бит.
    и int_fastN_t(int_fast8_t … int_fast64_t; uint_fast8_t … uint_fast64_t) — самый быстро обрабатываемый системой целочисленный тип шириной не менее N бит.

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *