Integer как хранится в памяти

Обновлено: 05.07.2024

Данные, обрабатываемые компьютером, - это константы и переменные. Все переменные в языке С++ должны быть описаны явно. Описание переменной включает спецификацию типа, имя переменной, ряд необязательных модификаторов, задающих или особенности внутреннего представления переменно, или класс её хранения. При описании возможно задание начального значения переменной (инициализация переменной). Тип константы распознаётся компилятором по её записи в программе.

Понятие «тип» - одно из фундаментальных для любого языка программирования. В С++ все типы разбиваются на 4 категории: «пустой» тип (void); скалярный тип; тип «функция»; агрегированный тип (агрегат).

К скалярным относят арифметические типы, перечисления, указатели, ссылочный тип (reference type). К агрегированным типам относят массивы, структуры и объединения и классы.

Кроме того, типы могут быть разделены на основные (фундаментальные) и производные (derived). К основным типам относят void, char, int, float, и double вместе с вариантами short, long, signed и unsigned, применимыми к некоторым из них.

В С++ есть набор основных типов, которые соответствуют наиболее общим основным единицам памяти компьютера и наиболее общим основным способам их использования:

char, short int, int, long int для представления целых различных размеров;

float, double для представления чисел с плавающей точкой;

unsigned char, unsigned short int, unsigned int, unsigned long int для представления беззнаковых целых, логических значений, битовых массивов и т.п.

Производные типы - это указатели и ссылки на другие типы, массивы, функции, классы, структуры и объединения.

Данные типа int

Тип «целое» (integer) включает данные типа char, short, int И long вместе с их вариантами signed и unsigned. Данные типа int (переменные и константы) могут быть в одной из следующих возможных форм, указанных в табл.1.

Данные типа int для компилятора Borland C++ 3.1

Тип Размер, Бит Диапазон представления чисел
Минимум Максимум
Unsigned int Short int Int Long Unsigned long -32 768 -32 768 -2 147 483 648 65 535 32 767 32 767 2 147 483 647 4 294 967 295

Как следует из таблицы, short int и int являются синонимами. Синонимами являются типы unsigned int и unsigned.

Язык С++ поддерживает IEEE – стандарт внутреннего представления данных типа int (рис.1).

Int S Значение числа
14 0

unsigned int Значение числа
15 0

long int S Значение числа
30 0

long unsigned Значение числа
31 0

S - знаковый разряд числа

Рис.1. IEEE - стандарт внутреннего представления данных типа int

В компьютере с 16-ти разрядным процессором данные типа int занимают 2-а байта памяти (машинное слово), данные типа long занимают 4-ре байта. Старший бит знаковых типов int, short int, long int хранит знак числа. Если он равен нулю, число положительное, если он равен единице – число отрицательное. Положительные числа хранятся в памяти и вступают в операции в прямом коде, т.е. в обычном двоичном представлении числа. Отрицательные числа хранятся в памяти компьютера в дополнительном коде. Приведем правило получения дополнительного кода:

модуль отрицательного числа записывается в прямом коде в битах 14-0 или 30-0 «прижатым» вправо. В неиспользуемые старшие биты записываются нули;

в знаковый разряд бит 15 или 31 записывается 1;

формируется обратный код битов 14-0 или 30-0; для этого нуль заменяется единицей, а единица нулем;

к обратному коду числа прибавляется единица.

Например, обратный код числа –33 в формате int:

14 0
000.0000.0010.0001 Прямой код
111.1111.1101.1110 Обратный код
+
111.1111.1101.1111 Дополнительный код FFDEh

Константы типа int могут задаваться в десятичной, восьмеричной и шестнадцатеричной системой счисления. Признаком константы в шестнадцатеричной системе счисления являются 0х (0Х) в качестве первых ее символов. Запись такой константы может содержать цифры от 0 до 9, а также символы шестнадцатеричных цифр A-F. Любая константа, начинающаяся с нуля, рассматривается компилятором как заданная в восьмеричной системе счисления.

Если в записи константы встречается суффикс L (l), компилятор интерпретирует константу как long. Суффикс U(u) явно указывает на константу типа unsigned. Разрешается комбинировать оба суффикса в любом порядке, например 976LU.

При отсутствии суффиксов L(l) или U(u) точный тип константы определяется по ее записи (табл.2). Отрицательные константы получаются применением операции «унарный минус» к соответствующей положительной константе.

Переменная типа int объявляется с использованием ключевых слов unsigned, int, short, long. Синонимами будут сочетания ключевых слов: signed int и int; unsigned int и unsigned; short, sort int и signed short int; unsigned short и unsigned short int; long, long int и signed long int; unsigned long и unsigned long int.

При описании переменной ей может быть задано начальное значение. Например:

int i=0xabcd, j=04567, k=1;

Целые константы и их тип при отсутствии суффиксов для компилятора

Записанное значение константы Тип, принимаемый компилятором
Десятичные константы: От 0 до 32 767 От 32 768 до 2 147 483 647 От 2 147 483 648 до 4 294 967 295 > 4 294 967 295 Int Long Unsigned long Генерируется ошибка
Восьмеричные константы: От 00 до 077777 От 0100000 до 0177777 От 02000000 до 017777777777 От 020000000000 до 037777777777 > 037777777777 Int Unsigned int Long Unsigned long Генерируется ошибка
Шестнадцатиричные константы: От 0x0000 до 0x7FFF От 0x8000 до 0xFFFF От 0x10000 до 0x7FFFFFFF От 0x80000000 до 0xFFFFFFFF > 0xFFFFFFFF Int Unsigned int Long Unsigned long Генерируется ошибка

Если в описании опущен тип, он предполагается int. Например:

все определяют объект типа int.

Данные типа char

Данные типа char занимают в памяти 1 байт. Код от 0 до 255 в этом байте задает один из 256 возможных символов. Закрепление конкретных символов за кодами задается так называемыми кодовыми таблицами. Для персональных компьютеров наиболее распространена ASCII-таблица.

Тип char является типом «целое». Данные типа char могут рассматриваться компилятором и как данные со знаком (signed char), и как данные без знака (unsigned char).

Если тип char рассматривается как signed, то старший бит его кода определяет знак. В случае unsigned char все восемь бит рассматриваются как код, а диапазон возможных значений от 0 до 255.

Константа типа char представляет собой символ заключенный в одиночные кавычки, например ‘A’, ‘$’. Внутренний код байта, соответствующего константе, определяется по кодовой таблице.

Представление группы символьных констант описаны ниже в параграфе 2.5.3.

Переменная типа char занимает 1 байт памяти и описывается с использованием ключевого слова char. При описании переменной ей можно задать начальное значение. Например:

char symbol=’\0100’; /* прописная латинская P */

char name=’0’, first=’\n’;

Данные с плавающей точкой

Язык Borland C++ поддерживает операции с плавающей точкой. Особенностью персональных компьютеров IBM PC является отсутствие в системе команд микропроцессора Intel, на базе которых они построены, операций над числами с плавающей точкой. Такие арифметические операции выполняет либо специальный сопроцессор математики с плавающей точкой (сопроцессоры 8087/80287/80387), либо при его отсутствии специальные стандартные подпрограммы (программная эмуляция сопроцессора).

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

Данные с плавающей точкой для компилятора Borland C++ 3.1

Тип Размер, бит Диапазон представления чисел(abs) Точность (десятичных цифр)
Максимум минимум
Float Double Long double 3.4*10 -38 1.7*10 -308 3.4*10 -4932 3.4*10 38 1.7*10 308 3.4*10 4932 19

Borland C++ поддерживает IEEE-стандарт, в соответствии с которым такие данные представляются в виде двух частей – мантиссы М и порядка Р числа в двоичной системе счисления:

Число бит для хранения мантиссы и порядка зависит от типа данных с плавающей точкой (рис. 2).

float: S P M
30 23 22 0
double: S P M
62 52 51 0
long double: S P M
78 64 63 0

S – знак числа (0 – для положительных, 1 – для отрицательных);

Р – сдвинутый порядок числа;

М – мантисса числа.

Рис. 2. IЕЕЕ-стандарт внутреннего представления данных с плавающей точкой.

Вещественное число в памяти хранится с нормализованной мантиссой. При нарушении нормализации мантиссу сдвигают влево до тех пор, пока старшей цифрой мантиссы не станет 1. Каждая операция сдвига сопровождается уменьшением порядка на 1. Но если мантисса всегда нормализована, то старшую едини­цу можно и не хранить в памяти. Это экономит один бит и, следовательно, увеличивает точность представления веществен­ных чисел. Эта единица присутствует неявно и называется неяв­ной единицей (implicit one). Отбрасывание старшей цифры мантиссы выполняется для форматов float и double, но не выполня­ется для long double.

Порядок числа хранится "сдвинутым", т. е. к нему при­бавляется число так, чтобы порядок был всегда неотрицатель­ным. Для чисел формата float прибавляется 127, для чисел формата double - 1023, для чисел формата long double -16383. Всегда неотрицательный порядок избавляет от необходимости выделять один бит для хранения знака порядка и упрощает выполнение операций сравнения порядков и арифметических операций над ними.

Например, число 15.375 (1111.011 в двоичной системе счисления) в формате float IEEE-стандарта записывается так:

Учитывая отбрасывание неявной единицы и сдвиг порядка, получаем внутреннее представление числа:

Р = 3+127 = 130(1000.0010 в двоичной системе счисления);

Переменные с плавающей точкой описываются с использованием спецификаторов типа float, double, long double.

Причина того, что представляется более чем один целый тип, более чем один беззнаковый тип и более чем один тип с плавающей точкой в том, чтобы дать возможность программисту воспользоваться характерными особенностями аппаратного обеспечения. На многих машинах между различными разновидностями основных типов существуют значительные различия в потребностях в памяти, временах доступа к памяти и временах вычислений. Размеры типов не указаны в стандарте языка а зависят от операционной системы, компилятора и разрядности процессора. В компьютерах с 16-ти разрядными процессорами int – 2 байта, в компьютерах с 32-х разрядными процессорами – 4 байта и т.д. В стандарте языка С++ указаны следующие соотношения относительно размеров основных типов:

I = = sizeof (char) < = sizeof(short) < = sizeof(int) < = sizeof(long)

sizeof (float) < = sizeof(double)

Однако обычно разумно предполагать, что в char могут храниться целые числа в диапазоне 0…127, что short и int имеют не менее 16 битов, что int имеет, по меньшей мере, 24 бита.

Беззнаковые (unsigned) целые типы идеально подходят для применений, в которых память рассматривается как массив битов.

Константы

С++ дает возможность записи значений основных типов: символьных констант, целых констант и констант с плавающей точкой. Кроме того, нуль (0) может использоваться как константа любого указательного типа, и символьные строки являются константами типа char[]. Можно также задавать символические константы. Символическая константа - это имя, значение которого не может быть изменено в области его видимости. В С++ имеется три механизма определения символических констант: 1) любому значению любого типа можно дать имя и использовать его как константу, добавив к его описанию ключевое слово const; 2) множество целых констант может быть определено как перечисление; 3) любое имя массива или функции является константой.

Целые константы

Целые константы предстают в четырех "обличиях": десятичные, восьмеричные, шестнадцатиричные и символьные константы. Десятичные используются чаще всего и выглядят так:

0 1234 5768595974474736

Десятичная константа имеет тип int, при условии, что она "влезает" в int, в противном случае ее тип – long суффикс L или l явно указывает компилятору, что тип константы – long, например 314564L или 3254354l. Компилятор должен предупреждать о константах, которые слишком длинны для представления в машине.

Константа, начинающаяся нулем, за которым идет x (0x), является шестнадцатеричным числом, а константа, начинающаяся нулем, за которым идет цифра, является восьмеричным числом. Например:

Наиболее важная концепция заключается в понимании разницы между числами и данными, которые эти числа представляют. Число — это абстрактное понятия, как исчислитель чего-то. У Вас есть десять пальцев. Понятие “десять” не меняется, в зависимости от использованного представления: десять, 10, diez (испанский), ju (японский), 1010 (бинарное представление), Х (римские числа)… Все эти представления указывают на понятие “десяти”.

Сравним это с данными. Данные — это физическое понятие, просто последовательность битов и байтов, хранящихся на компьютере. Данные не имеют неотъемлемого значения и должны быть интерпретированы тем, кто их считывает.

Данные — это как человеческое письмо, просто набор отметок на бумаге. Этим отметкам не присуще какое-либо значение. Если мы видим линию и круг (например, |O), то можно интерпретировать это как “десять”. Но это лишь предположение, что считанные символы представляют число. Это могут быть буквы “IO” — название спутника Юпитера. Или, возможно, имя греческой богини. Или аббревиатура для ввода/вывода. Или чьи-то инициалы. Или число 2 в бинарном представлении (“10”). Этот список предположений можно продолжить. Дело в том, что один фрагмент данных (|O) может быть интерпретировано по разному, и смысл остается не ясен, пока кто-то не уточнит намерения автора.

Компьютеры сталкиваются с такой же проблемой. Они хранят данные, а не абстрактные понятия, используя при этом 1 и 0. Позднее они считывают эти 1 и 0 и пытаются воссоздать абстрактные понятия из набора данных. В зависимости от сделанных допущений, эти 1 и 0 могут иметь абсолютно разное значение.

Почему так происходит? Ну, вообще-то нет такого правила, что компьютеры должны использовать один и тот же язык, так же, как нет такого правила и для людей. Каждый компьютер одного типа имеет внутреннюю совместимость (он может считывать свои собственные данные), но нет никакой гарантии, как именно интерпретирует эти данные компьютер другого типа.

  • Данные (биты и байты или отметки на бумаге) сами по себе не имеют смысла. Они должны быть интерпретированы в какое-то абстрактное понятие, например, число.
  • Как и люди, компьютеры имеют различные способы хранения одного и того же абстрактного понятия (например, мы можем различными способами сказать “10”).

Храним числа как данные

  • Бит имеет два состояния (включен или выключен, 1 или 0).
  • Байт — это последовательность из 8 бит. Крайний левый бит в байте является старшим. То есть двоичная последовательность 00001001 является десятичным числом девять. 00001001 = (2^3 + 2^0 = 8 + 1 = 9).
  • Биты нумеруются справа налево. Бит 0 является крайним правым и он наименьший. Бит 7 является крайним левым и он наибольший.

Так в чем же проблема — компьютеры отлично ладят с одиночными байтами, правда? Ну, все превосходно для однобайтных данных, таких как ASCII-символы. Однако, много данных используют для хранения несколько байтов, например, целые числа или числа с плавающей точкой. И нет никакого соглашения о том, в каком порядке должны хранится эти последовательности.

Пример с байтом

Рассмотрим последовательность из 4 байт. Назовем их W X Y и Z. Я избегаю наименований A B C D, потому что это шестнадцатеричные числа, что может немного запутывать. Итак, каждый байт имеет значение и состоит из 8 бит.


Например, W — это один байт со значением 0х12 в шестнадцатеричном виде или 00010010 в бинарном. Если W будет интерпретироваться как число, то это будет “18” в десятеричной системе (между прочим, ничто не указывает на то, что мы должны интерпретировать этот байт как число — это может быть ASCII-символ или что-то совсем иное). Вы все еще со мной? Мы имеем 4 байта, W X Y и Z, каждый с различным значением.

Понимаем указатели

Указатели являются ключевой частью программирования, особенно в языке С. Указатель представляет собой число, являющееся адресом в памяти. И это зависит только от нас (программистов), как интерпретировать данные по этому адресу.

В языке С, когда вы кастите (приводите) указатель к конкретному типу (такому как char * или int *), это говорит компьютеру, как именно интерпретировать данные по этому адресу. Например, давайте объявим:


Обратите внимание, что мы не можем получить из р данные, потому что мы не знаем их тип. р может указывать на цифру, букву, начало строки, Ваш гороскоп или изображение — мы просто не знаем, сколько байт нам нужно считать и как их интерпретировать.

Теперь предположим, что мы напишем:


Этот оператор говорит компьютеру, что р указывает на то же место, и данные по этому адресу нужно интерпретировать как один символ (1 байт). В этом случае, с будет указывать на память по адресу 0, или на байт W. Если мы выведем с, то получим значение, хранящееся в W, которое равно шестнадцатеричному 0x12 (помните, что W — это полный байт). Этот пример не зависит от типа компьютера — опять же, все компьютеры одинаково хорошо понимают, что же такое один байт (в прошлом это было не всегда так).

Этот пример полезен, он одинаково работает на все компьютерах — если у нас есть указатель на байт (char *, один байт), мы можем проходить по памяти, считывая по одному байту за раз. Мы можем обратиться к любому месту в памяти, и порядок хранения байт не будет иметь никакого значения — любой компьютер вернет нам одинаковую информацию.

Так в чем же проблема?

Проблемы начинаются, когда компьютер пытается считать несколько байт. Многие типы данных состоят больше чем из одного байта, например, длинные целые (long integers) или числа с плавающей точкой. Байт имеет только 256 значений и может хранить числа от 0 до 255.

  • Машины с порядком хранения от старшего к младшему (прямой порядок) хранят старший байт первым. Если посмотреть на набор байтов, то первый байт (младший адрес) считается старшим.
  • Машины с порядком хранения от младшего к старшему (обратный порядок) хранят младший байт первым. Если посмотреть на набор байт, то первый байт будет наименьшим.

Повторюсь, порядок следования байтов не имеет значения пока Вы работаете с одним байтом. Если у Вас есть один байт, то это просто данные, которые Вы считываете и есть только один вариант их интерпретации (опять таки, потому что между компьютерами согласовано понятие одного байта).

Теперь предположим, что у нас есть 4 байта (WXYZ), которые хранятся одинаково на машинах с обоими типами порядка записи байтов. То есть, ячейка памяти 0 соответствует W, ячейка 1 соответствует X и т. д.

Мы можем создать такое соглашение, помня, что понятие “байт” является машинно-независимым. Мы можем обойти память по одному байту за раз и установить необходимые значения. Это будет работать на любой машине.


Такой код будет работать на любой машине и успешно установит значение байт W, X, Y и Z расположенных на соответствующих позициях 0, 1, 2 и 3.

Интерпретация данных

Теперь давайте рассмотрим пример с многобайтными данными (наконец-то!). Короткая сводка: “short int” это 2-х байтовое число (16 бит), которое может иметь значение от 0 до 65535 (если оно беззнаковое). Давайте используем его в примере.

  • Машина с прямым порядком хранения: Я думаю, short int состоит из двух байт, а значит я считаю их. Позиция s это адрес 0 (W или 0х12), а позиция s + 1 это адрес 1 (X или 0х34). Поскольку первый байт является старшим, то число должно быть следующим 256 * байт 0 + байт 1 или 256 * W + X, или же 0х1234. Я умножаю первый байт на 256 (2^8) потому что его нужно сдвинуть на 8 бит.
  • Машина с обратным порядком хранения: Я не знаю что курит мистер “От старшего к младшему”. Я соглашусь, что short int состоит из 2 байт и я считаю их точно также: позиция s со значение 0х12 и позиция s + 1 со значением 0х34. Но в моем мире первым является младший байт! И число должно быть байт 0 + 256 * байт 1 или 256 * X + W, или 0х3412.

Теперь Вы видите проблему? Машина с порядком хранения от старшего к младшему считает, что s = 0x1234, в то время как машина с порядком хранения от младшего к старшему думает, что s = 0x3412. Абсолютно одинаковые данные дают в результате два совершенно разных числа.

И еще один пример

Давайте для “веселья” рассмотрим еще один пример с 4 байтовым целым:

  • Машина с прямым порядком хранения: тип int состоит из 4 байт и первый байт является старшим. Считываю 4 байта (WXYZ) из которых старший W. Полученное число: 0х12345678.
  • Машина с обратным порядком хранения: несомненно, int состоит из 4 байт, но старшим является последний. Так же считываю 4 байта (WXYZ), но W будет расположен в конце — так как он является младшим. Полученное число: 0х78563412.

Проблема NUXI

Проблему с порядком байт иногда называют проблемой NUXI: слово UNIX, сохраненное на машинах с порядком хранения от старшего к младшему, будет отображаться как NUXI на машинах с порядком от младшего к старшему.

Допустим, что мы собираемся сохранить 4 байта (U, N, I, и X), как два short int: UN и IX. Каждая буква занимает целый байт, как в случае с WXYZ. Для сохранения двух значений типа short int напишем следующий код:


Этот код не является специфичным для какой-то машины. Если мы сохраним значение “UN” на любой машине и считаем его обратно, то обратно получим тоже “UN”. Вопрос порядка следования байт не будет нас волновать, если мы сохраняем значение на одной машине, то должны получить это же значение при считывании.

Однако, если пройтись по памяти по одному байту за раз (используя трюк с char *), то порядок байт может различаться. На машине с прямым порядком хранения мы увидим:


Что имеет смысл. “U” является старшим байтом в “UN” и соответственно хранится первым. Такая же ситуация для “IX”, где “I” — это старший байт и хранится он первым.

На машине с обратным порядком хранения мы скорее всего увидим:


Но и это тоже имеет смысл. “N” является младшим байтом в “UN” и значит хранится он первым. Опять же, хотя байты хранятся в “обратном порядке” в памяти, машины с порядком хранения от младшего к старшему знают что это обратный порядок байт, и интерпретирует их правильно при чтении. Также, обратите внимание, что мы можем определять шестнадцатеричные числа, такие как 0x1234, на любой машине. Машина с обратным порядком хранения байтов знает, что Вы имеете в виду, когда пишите 0x1234 и не заставит Вас менять значения местами (когда шестнадцатеричное число отправляется на запись, машина понимает что к чему и меняет байты в памяти местами, скрывая это от глаз. Вот такой трюк.).

Рассмотренный нами сценарий называется проблемой “NUXI”, потому что последовательность “UNIX” интерпретируется как “NUXI” на машинах с различным порядком хранения байтов. Опять же, эта проблема возникает только при обмене данными — каждая машина имеет внутреннюю совместимость.

Обмен данными между машинами с различным порядком хранения байтов

Сейчас компьютеры соединены — прошли те времена, когда машинам приходилось беспокоиться только о чтении своих собственных данных. Машинам с различным порядком хранения байтов нужно как-то обмениваться данными и понимать друг друга. Как же они это делают?

Решение 1: Использовать общий формат

Самый простой подход состоит в согласовании с общим форматом для передачи данных по сети. Стандартным сетевым является порядок от старшего к младшему, но некоторые люди могут расстроиться, что не победил порядок от младшего к старшему, поэтому просто назовем его “сетевой порядок”.

Для конвертирования данных в соответствии с сетевым порядком хранения байтов, машины вызывают функцию hton() (host-to-network). На машинах с прямым порядком хранения эта функция не делает ничего, но мы не будем говорить здесь об этом (это может разозлить машины с обратным порядком хранения :) ).

Но важно использовать функцию hton() перед отсылкой данных даже если Вы работаете на машине с порядком хранения от старшего к младшему. Ваша программа может стать весьма популярной и будет скомпилирована на различных машинах, а Вы ведь стремитесь к переносимости своего кода (разве не так?).

Точно также существует функция ntoh() (network-to-host), которая используется для чтения данных из сети. Вы должны использовать ее, чтобы быть уверенными, что правильно интерпретируете сетевые данные в формат хоста. Вы должны знать тип данных, которые принимаете, чтобы расшифровать их правильно. Функции преобразования имеют следующий вид:


Помните, что один байт — это один байт и порядок не имеет значения.

Эти функции имеют критическое значение при выполнении низкоуровневых сетевых операций, таких как проверка контрольной суммы IP-пакетов. Если Вы не понимаете сути проблемы с порядком хранения байтов, то Ваша жизнь будет наполнена болью — поверьте мне на слово. Используйте функции преобразования и знайте, зачем они нужны.

Решение 2: Использования маркера последовательности байтов (Byte Order Mark — BOM)

Этот подход подразумевает использование некого магического числа, например 0xFEFF, перед каждым куском данных. Если Вы считали магическое число и его значение 0xFEFF, значит данные в том же формате, что и у Вашей машины и все хорошо. Если Вы считали магическое число и его значение 0xFFFE, это значит, что данные были записаны в формате, отличающемся от формата вашей машины и Вы должны будете преобразовать их.

Нужно отметить несколько пунктов. Во-первых, число не совсем магическое, как известно программисты часто используют этот термин для описания произвольно выбранных чисел (BOM может быть любой последовательностью различных байтов). Такая пометка называется маркером последовательности байтов потому что показывает в каком порядке данные были сохранены.

Во-вторых, BOM добавляет накладные расходы для всех передаваемых данных. Даже в случае передачи 2 байт информации Вы должны добавлять к ним 2 байта маркера BOM. Пугающе, не так ли?

Unicode использует BOM, когда сохраняет многобайтные данные (некоторые кодировки Unicode могут иметь по 2, 3 и даже 4 байта на символ). XML позволяет избежать этой путаницы, сохраняя данные сразу в UTF-8 по умолчанию, который сохраняет информацию Unicode по одному байту за раз. Почему это так круто?

Повторяю в 56-й раз — потому что проблема порядка хранения не имеет значения для единичных байт.

Опять же, в случае использования BOM может возникнуть другие проблемы. Что, если Вы забудете добавить BOM? Будете предполагать, что данные были отправлены в том же формате, что и Ваши? Прочитаете данные и, увидев что они “перевернуты” (что бы это не значило), попытаетесь преобразовать их? Что, если правильные данные случайно будут содержать неправильный BOM? Эти ситуации не очень приятные.

Почему вообще существует эта проблема? Нельзя ли просто договориться?

Ох, какой же это философский вопрос. Каждый порядок хранения байтов имеет свои преимущества. Машины с порядком следования от младшего к старшему позволяют читать младший байт первым, не считывая при этом остальные. Таким образом можно легко проверить является число нечетным или четным (последний бит 0), что очень здорово, если Вам необходима такая проверка. Машины с порядком от старшего к младшему хранят данные в памяти в привычном для человека виде (слева направо), что упрощает низкоуровневую отладку.

Так почему же все просто не договорятся об использовании одной из систем? Почему одни компьютеры пытаются быть отличными от других? Позвольте мне ответить вопросом на вопрос: почему не все люди говорят на одном языке? Почему в некоторых языках письменность слева направо, а у других справа налево?

Иногда системы развиваются независимо, а в последствии нуждаются во взаимодействии.

На этом уроке мы рассмотрим целочисленные типы данных в языке С++, их диапазоны значений, операцию деления, а также переполнение (что это такое и примеры).

Целочисленные типы данных

Целочисленный тип данных — это тип, переменные которого могут содержать только целые числа (без дробной части, например: -2, -1, 0, 1, 2). В языке C++ есть 5 основных целочисленных типов, доступных для использования:

Тип Минимальный размер
Символьный тип данных char 1 байт
Целочисленный тип данных short 2 байта
int 2 байта (но чаще всего 4 байта)
long 4 байта
long long 8 байт

Основным различием между целочисленными типами, перечисленными выше, является их размер, чем он больше, тем больше значений сможет хранить переменная этого типа.

Объявление целочисленных переменных

Объявление происходит следующим образом:

В то время как полные названия short int , long int и long long int могут использоваться, их сокращенные версии (без int ) более предпочтительны для использования. К тому же постоянное добавление int затрудняет чтение кода (легко перепутать с именем переменной).

Диапазоны значений и знак целочисленных типов данных

Как вы уже знаете из предыдущего урока, переменная с n-ным количеством бит может хранить 2 n возможных значений. Но что это за значения? Это значения, которые находятся в диапазоне. Диапазон — это значения от и до, которые может хранить определенный тип данных. Диапазон целочисленной переменной определяется двумя факторами: её размером (измеряется в битах) и её знаком (который может быть signed или unsigned).

Целочисленный тип signed (со знаком) означает, что переменная может содержать как положительные, так и отрицательные числа. Чтобы объявить переменную как signed, используйте ключевое слово signed :

По умолчанию, ключевое слово signed пишется перед типом данных.

1-байтовая целочисленная переменная со знаком (signed) имеет диапазон значений от -128 до 127, т.е. любое значение от -128 до 127 (включительно) может храниться в ней безопасно.

В некоторых случаях мы можем заранее знать, что отрицательные числа в программе использоваться не будут. Это очень часто встречается при использовании переменных для хранения количества или размера чего-либо (например, ваш рост или вес не может быть отрицательным).

Целочисленный тип unsigned (без знака) может содержать только положительные числа. Чтобы объявить переменную как unsigned, используйте ключевое слово unsigned :

1-байтовая целочисленная переменная без знака (unsigned) имеет диапазон значений от 0 до 255.

Обратите внимание, объявление переменной как unsigned означает, что она не сможет содержать отрицательные числа (только положительные).

Теперь, когда вы поняли разницу между signed и unsigned, давайте рассмотрим диапазоны значений разных типов данных:

Размер/Тип Диапазон значений
1 байт signed от -128 до 127
1 байт unsigned от 0 до 255
2 байта signed от -32 768 до 32 767
2 байта unsigned от 0 до 65 535
4 байта signed от -2 147 483 648 до 2 147 483 647
4 байта unsigned от 0 до 4 294 967 295
8 байтов signed от -9 223 372 036 854 775 808 до 9 223 372 036 854 775 807
8 байтов unsigned от 0 до 18 446 744 073 709 551 615

Для математиков: Переменная signed с n-ным количеством бит имеет диапазон от -(2 n-1 ) до 2 n-1 -1. Переменная unsigned с n-ным количеством бит имеет диапазон от 0 до (2 n )-1.

Для нематематиков: Используем таблицу 🙂

Что используется по умолчанию: signed или unsigned?

Так что же произойдет, если мы объявим переменную без указания signed или unsigned?

Тип По умолчанию
Символьный тип данных char signed или unsigned (в большинстве случаев signed)
Целочисленный тип данных short signed
int signed
long signed
long long signed

Все целочисленные типы данных, кроме char, являются signed по умолчанию. Тип char может быть как signed, так и unsigned (но, обычно, signed).

В большинстве случаев ключевое слово signed не пишется (оно и так используется по умолчанию).

Программисты, как правило, избегают использования целочисленных типов unsigned, если в этом нет особой надобности, так как с переменными unsigned ошибок, по статистике, возникает больше, нежели с переменными signed.

Правило: Используйте целочисленные типы signed, вместо unsigned.

Переполнение

На уроке №28 мы говорили о том, что данные хранятся в бинарном (двоичном) формате и каждый бит может иметь только 2 возможных значения ( 0 или 1 ). Вот как выглядит диапазон чисел от 0 до 15 в десятичной и двоичной системах:

Десятичная система Двоичная система
0 0
1 1
2 10
3 11
4 100
5 101
6 110
7 111
8 1000
9 1001
10 1010
11 1011
12 1100
13 1101
14 1110
15 1111

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

Примеры переполнения

Рассмотрим переменную unsigned, которая состоит из 4 бит. Любое из двоичных чисел, перечисленных в таблице выше, поместится внутри этой переменной.

Например, если мы попытаемся поместить число 21 в нашу 4-битную переменную:

Десятичная система Двоичная система
21 10101

Число 21 занимает 5 бит (10101). 4 бита справа (0101) поместятся в переменную, а крайний левый бит (1) просто потеряется. Т.е. наша переменная будет содержать 0101, что равно 101 (нуль спереди не считается), а это уже число 5, а не 21.

Теперь рассмотрим пример в коде (тип short занимает 16 бит):

unsigned short x = 65535 ; // наибольшее значение, которое может хранить 16-битная unsigned переменная x = x + 1 ; // 65536 - это число больше максимально допустимого числа из диапазона допустимых значений. Следовательно, произойдет переполнение, так как переменнная x не может хранить 17 бит

Результат выполнения программы:

x was: 65535
x is now: 0

Что случилось? Произошло переполнение, так как мы попытались присвоить переменной x значение больше, чем она способна в себе хранить.

Аналогичным образом, мы получим переполнение, использовав число меньше минимального из диапазона допустимых значений:

unsigned short x = 0 ; // наименьшее значение, которое 2-байтовая unsigned переменная может хранить

Результат выполнения программы:

x was: 0
x is now: 65535

Переполнение приводит к потере информации, а это никогда не приветствуется. Если есть хоть малейшее подозрение или предположение, что значением переменной может быть число, которое находится вне диапазона допустимых значений используемого типа данных — используйте тип данных побольше!

Правило: Никогда не допускайте возникновения переполнения в ваших программах!

Деление целочисленных переменных

В языке C++ при делении двух целых чисел, где результатом является другое целое число, всё довольно предсказуемо:

Но что произойдет, если в результате деления двух целых чисел мы получим дробное число? Например:

В языке C++ при делении целых чисел результатом всегда будет другое целое число. А такие числа не могут иметь дробь (она просто отбрасывается, не округляется!).

Рассмотрим детально вышеприведенный пример: 8 / 5 = 1.6 . Но как мы уже знаем, при делении целых чисел результатом является другое целое число. Таким образом, дробная часть ( 0.6 ) значения отбрасывается и остается 1 .

Правило: Будьте осторожны при делении целых чисел, так как любая дробная часть всегда отбрасывается.

(623 оценок, среднее: 4,94 из 5)

Урок №30. Размер типов данных

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

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

Всем доброго времени суток. Появился такой вопрос: для объявления без знакового числа, для плюсов, обязательно писать unsigned int X, есть ли сокращенная форма по типу uint X?

Может проще для запоминания было сказать, что тип signed (со знаком) использует 1 (старший бит в байте для записи этого самого знака и для самого числа остается 7 бит (это в случае 1-го байта, для 2- байт 15 и т.д.) и в 7 битах можно записать число не больше чем 128.
К примеру 10000000 это отрицательный ноль. 🙂 Но такого не бывает.

хехе, а попробуйте к знаковому 2 байтному целоисчисленной переменной со значением 32767 прибавить 1, или так-же наоборот от -32768 отнять 1

Для того, чтоб числа имели дробь при делении целых чисел можно приписать ноль после точкой. Например : 8.0/5.0 = 1.6

Только это уже совсем другая история)

Достаточно поставить точку одному из выражений. Например: 8. / 5 или 8 / 5.

Остальное компилятор сам подставит)

В некоторых источниках встречал, что в связи со знаковостью, появляются два варианта нуля: +0 и -0. Почему-то тут этот вопрос не затронут. Ему перестали предавать значение в сообществе?

Скорее всего это какой-то очень древний подход. Никогда не слышал подобного в универе.

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

unsigned используется для экономии памяти, это же очевидно. Если знак действительно не нужен за счет дополнительно освобожденного бита, можно увеличить диапазон значений в 2 раза, что в некоторых случаях позволит использовать более "экономные" типы данных.

так если при делении дробная часть отбрасывается ,то как создать калькулятор?Если он не будут выводить дробные числа.Или ответ стоит присвоить к переменной которая будет иметь тип float?

Ну так нужно указывать другой тип переменной(не целое число). Тогда будет дробь.

Забавная история, почему этот урок так важен =)
В игре Civilization есть баг с механикой агрессии и миролюбия. Суть такова, что агрессивность цивилизации измерялась по шкале от 1 до 10. Девятки и десятки были у всяких Чингисханов, Монтесум и Сталиных, а у духовного пацифиста Махатмы Ганди была единичка. И ещё были модификаторы — строй «республика» уменьшает агрессивность на 1, «демократия» — на 2. Соответственно, сразу же, как только индусы открывали Демократию, у Ганди становилась агрессивность −1.

А теперь внимание. Эта переменная была однобайтная и строго неотрицательная(unsigned), от 0 до 255. Соответственно, агрессивность Махатмы Ганди становилась равна 255 из 10. Поэтому, построив у себя демократию, Ганди двигался рассудком, клепал ядрёные бомбы и умножал всех на ноль.


char *mem = (char*) &i; mem[0], mem[1], mem[2], mem[3] содержат первые 4 байта представления числа i.


Вопрос есть к знатокам: такая конструкция корректно порядок байт для BE/LE покажет?


Ты бит с байтом перепутал? o_O


Да, конечно, почему нет?

Кстати, union-ы лучше, чем приведение типов указателей (gcc с -O2 может и нули показать в более сложных случаях, man strict aliasing).

char* может алиасить всё что угодно.


Да? Ну может и так, я на всякий случай упомянул :)

Читал просто как-то про union и геморой с выравниваниями. Вот с тех пор (uchar *) и пользую. Решил спросить - может у кого опыт применения в реальной жизни есть.

читай про структуры, там выравнивание тоже самое.


Да, вроде как если платформа такая злобная, что адресовать умеет только по 4-хбайтной границе, только сдвиганием в явном виде можно получить куски. Но так не узнаешь, что там реально в памяти в каком порядке.


если знать как байты (2ух или четырёх или восьмибайтного инта хранятся ) то присваиваеш в нулевой области (на 256) уникальные байты а потом по известному адрессу читаеш как инт и смотриш что за число прочиталось из этого понятно станет младшие впереди , старшие впереди , или как в сфэйлившем по этому 32битном pdp11(который был биг эндиан на 16битах и убейся об стену от произвола и сиюминутного решения на 32 битах) или вообще перестановка 8 элиментов(их как раз 8!) - вдуг у этого конкретного имбедеда так НУЖНО.


по анси вроде гарантируется тока непрерывность памяти которая достаточна для хранения 2в 16 различных состояний хоть гигабайт на инт и битики размазаны в художественном беспорядке.

Все наоборот. PDP-11 хранит байты в слове в LE-порядке, а две с половиной 32-разрядных инструкции обращаются к словам внутри двойного слова как BE.


ок . фишка про которую считаю важно помнить и знать что нашлись идиоты и/или практическая необходимость скрестить 2 разных направления размещения старшей и младшей половины при разбещении 4 байт.

и всё таки pdp11 вроде старший байт слова хранил по меньшему адресу

короче если 4 байта в арабской записи по основанию 256 записать как x01x02x03x04 то в памяти оно лежало как (x02x01x04x03 либо x03x04x01x02) в отличии от чистого младшие вперёд(BE?) (x04x03x02x01) и чистого старшие раньше(x01x02x03x04)


Если вы хотите хранить в переменных целые числа , вам нужно использовать тип int .

int — это сокращение от Int eger ( целый с английского), что как бы намекает, что этот тип позволяет хранить целые числа .

Переменные типа int способны хранить целые числа в диапазоне от - 2 миллиарда до + 2 миллиарда . Или, если быть более точным, то от - 2,147,483,648 до 2,147,483,647 .

Такие некруглые значения связаны с устройством памяти компьютера.

В Java для типа int выделено 4 байта памяти. Каждый байт памяти состоит из 8 битов . Каждый бит может принимать только 2 значения — 0 или 1. Переменная типа int содержит 32 бита и может принимать 4,294,967,296 значений.

Половину этого диапазона отдали под отрицательные числа, а вторую — под положительные. Вот и получилось как раз от - 2,147,483,648 до 2,147,483,647 .

2. Создание переменной типа int

Тип int предназначен для хранения целых чисел. Чтобы создать в коде переменную, которая будет способна хранить целые числа , нужно воспользоваться командой вида:

Где имя — это имя переменной. Примеры:

Команда Описание
Создается целочисленная переменная x
Создается целочисленная переменная count
Создается целочисленная переменная currentYear

Размер (регистр) букв имеет значение: команды int c olor и int C olor объявят две разные переменные.

Команды же Int Color и INT COLOR компилятор вообще не поймет и сообщит об ошибке. int — это специальное слово, обозначающее целый тип, и пишется оно только строчными буквами .

3. Краткая запись создания переменных

Если в одном месте программы нужно создать много переменных одного типа, это можно сделать, используя сокращенную запись:

Краткая запись создания нескольких переменных одного типа

Команды Краткая запись тех же команд

4. Присваивание значений

Чтобы занести значение в переменную типа int , нужно воспользоваться командой:

Где значением может быть любое целочисленное выражение. Примеры:

Команда Примечание
Не скомпилируется, т.к. 3,000,000,000 больше, чем максимально возможное значение типа int 2,147,483,647

5. Сокращенная запись создания и инициализации переменной

Создание (объявление) переменной и присваивание ей значения можно записать одной командой. Чаще всего так и делают, т. к. переменная обычно объявляется тогда, когда возникает необходимость сохранить какое-либо значение.

Выглядит эта команда так:

Краткая запись создания и инициализации переменной

Команда Примечание
В переменной будет значение «2 миллиарда»
В переменной будет значение «минус 10 миллионов»
Не скомпилируется, т. к. 3,000,000,000 больше, чем максимально возможное значение типа int: 2,147,483,647

Можно объявить и несколько переменных одной строкой. Тогда команда будет иметь вид:

Читайте также: