Нумерация байт оперативной памяти каждой виртуальной машины начинается с
Нумерация байт оперативной памяти каждой виртуальной машины начинается с
Память и процессор
Среди устройств и узлов, входящих в состав компьютера, наиболее важными для выполнения любой программы катаются оперативная память и центральный микропроцессор, который мы для краткости будем в дальнейшем называть просто процессором. В оперативной памяти хранится выполняемая программа вместе с принадлежащими ей данными; процессор выполняет вычисления и другие действия, описанные в программе.
Программа загружается в память с жесткого или гибкого магнитного диска, где она хранится, операционной системой в ответ ввод с клавиатуры команды запуска программы. Операционная система, загрузив программу, и при необходимости настроив ее для выполнения в той области памяти, куда она попала, сообщает процессору начальный адрес загруженной программы и инициирует процесс ее выполнения.
Процессор считывает из памяти первую команду программы, находит в памяти или в своих регистрах данные, необходимые для ее выполнения (если, конечно, команда требует данных) и, выполнив требуемую операцию, возвращает в память или, возможно, оставляет в регистрах результат своей работы (рис. 1.1).
Рис. 1.1. Взаимодействие оперативной памяти и процессора.
Рис. 1.2. Байт, слово и двойное слово.
Рис. 1.3. Нумерация байтов в многобайтовых данных.
Рис. 1.4. Образование физического адреса из сегментного адреса и смещения.
Нумерация байт оперативной памяти каждой виртуальной машины начинается с
Электронные облака
Лекции
Рабочие материалы
Тесты по темам
Template tips
Задачи
Логика вычислительной техники и программирования
Лекция «Представление информации в компьютере. Структура внутренней памяти.»
Основные понятия: бит, байт, дискретность, отрицательные числа, дополнительный код, двоичные и шестнадцатеричные числа, целые и вещественные числа, мантисса, машинный порядок, нормализованное представление, машинное слово, адресуемость.
Биты и байты
Написание программ требует знаний организации всей системы компьютера. В основе компьютера лежат понятия бита и байта. Они являются тем средством, благодаря которым в компьютерной памяти представлены данные и команды.
Для выполнения программ компьютер временно записывает программу и данные в основную память. Компьютер имеет также ряд pегистров, которые он использует для временных вычислений.
Минимальной единицей информации в компьютере является бит.
Бит – ячейка памяти, хранящая один двоичный знак. Битовая структура памяти определяет первое свойство памяти – дискретность.
Бит может быть выключен, так что его значение есть нуль, или включен, тогда его значение равно единице. Единственный бит не может представить много информации в отличие от группы битов.
Во внутренней памяти компьютера все байты пронумерованы. Нумерация начинается с нуля. Порядковый номер называется его адресом. В компьютере адреса обозначаются двоичным кодом. Используется также шестнадцатеричная форма обозначения адреса.
Двоичные числа
Так как компьютер может различить только нулевое и единичное состояние бита, то он работает системе исчисления с базой 2 или в двоичной системе. Фактически бит унаследовал свое название от английского «BInary digiT» (двоичная цифра).
Сочетанием двоичных цифр (битов) можно представить любое значение. Значение двоичного числа определяется относительной позицией каждого бита и наличием единичных битов. Ниже показано восьмибитовое число, содержащее все единичные биты:
Позиционные веса | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
Включенные биты | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
Двоичное сложение
Микрокомпьютер выполняет арифметические действия только в двоичном формате.
0 + 0 = 0
1 + 0 = 1
1 + 1 = 10
1 + 1 + 1 = 11
Обратите внимание на перенос единичного бита в последних двух операциях. Теперь, давайте сложим 01000001 и 00101010.(число 65 и число 42):
Двоичные | Десятичные |
01000001 | 65 |
00101010 | 42 |
01101011 | 107 |
Проверьте, что двоичная сумма 01101011 действительно равна 107. Рассмотрим другой пример:
Двоичные | Десятичные |
00111100 | 60 |
00110101 | 53 |
01110001 | 113 |
Представление целых чисел
Множество целых чисел, представимых в памяти ЭВМ, ограничено. Диапазон значений зависит от размера ячеек памяти, используемых для их хранения. В k-разрядной ячейке может храниться 2 k различных значений целых чисел.
Чтобы получить внутреннее представление целого положительного числа N, хранящегося в k-разрядном машинном слове, необходимо:
1) перевести число N в двоичную систему счисления;
2) полученный результат дополнить слева незначащими нулями до k разрядов
Отрицательные числа
Для записи внутреннего представления целого отрицательного числа (-N) необходимо:
1) получить внутреннее представление положительного числа N;
2) получить обратный код этого числа заменой 0 на 1 и 1на 0;
3) к полученному числу прибавить 1.
Данная форма представления целого отрицательного числа называется дополнительным кодом. Использование дополнительного кода позволяет заменить операцию вычитания на операцию сложения уменьшаемого числа с дополнительным кодом вычитаемого.
Двоичные разряды в ячейке памяти нумеруются от 0 до k справа налево. Старший, k-й разряд во внутреннем представлении любого положительного числа равен нулю, отрицательного числа – единице. Поэтому этот разряд называется знаковым разрядом.
65 | 01000001 |
+(-42) | 11010110 |
23 | (i)00010111 |
Результат 23 является корректным. В рассмотренном примере произошел перенос в знаковый разряд и из разрядной сетки.
Шестнадцатеричное представление
Представим, что необходимо просмотреть содержимое некоторых байт в памяти. Требуется определить содержимое четырех последовательных байт (двух слов), которые имеют двоичные значения. Так как четыре байта включают в себя 32 бита, то специалисты разработали «стенографический» метод представления двоичных данных. По этому методу каждый байт делится пополам и каждые полбайта выражаются соответствующим значением. рассмотрим следующие четыре байта:
Двоичное | 0101 | 1001 | 001 | 0101 | 1011 | 1001 | 110 | 1110 |
Десятичное | 5 | 9 | 3 | 5 | 11 | 9 | 12 | 14 |
Так как здесь для некоторых чисел требуется две цифры, расширим систему счисления так, чтобы 10=A, 11=B, 12=C, 13=D, 14=E, 15=F. Таким образом, получим более сокращенную форму, которая представляет содержимое вышеуказанных байт:
59 | 35 | B9 | CE |
Такая система счисления включает «цифры» от 0 до F, и так как таких цифр 16, она называется шестнадцатеричным представлением.
Шестнадцатеричный формат нашел большое применение в языке ассемблера.
Если немного поработать с шестнадцатеричным форматом, то можно быстро привыкнуть к нему.
Следует помнить, что после шестнадцатеричного числа F следует шестнадцатеричное 10, что равно десятичному числу 16.
Машинное слово
Вся информация (данные) представлена в виде двоичных кодов. Для удобства работы введены следующие термины, обозначающие совокупности двоичных разрядов (см. табл.). Эти термины обычно используются в качестве единиц измерения объемов информации, хранимой или обрабатываемой в компьютере.
Разбираемся с прямым и обратным порядком байтов
Числа и данные
Наиболее важная концепция заключается в понимании разницы между числами и данными, которые эти числа представляют. Число — это абстрактное понятия, как исчислитель чего-то. У Вас есть десять пальцев. Понятие “десять” не меняется, в зависимости от использованного представления: десять, 10, diez (испанский), ju (японский), 1010 (бинарное представление), Х (римские числа)… Все эти представления указывают на понятие “десяти”.
Сравним это с данными. Данные — это физическое понятие, просто последовательность битов и байтов, хранящихся на компьютере. Данные не имеют неотъемлемого значения и должны быть интерпретированы тем, кто их считывает.
Данные — это как человеческое письмо, просто набор отметок на бумаге. Этим отметкам не присуще какое-либо значение. Если мы видим линию и круг (например, |O), то можно интерпретировать это как “десять”. Но это лишь предположение, что считанные символы представляют число. Это могут быть буквы “IO” — название спутника Юпитера. Или, возможно, имя греческой богини. Или аббревиатура для ввода/вывода. Или чьи-то инициалы. Или число 2 в бинарном представлении (“10”). Этот список предположений можно продолжить. Дело в том, что один фрагмент данных (|O) может быть интерпретировано по разному, и смысл остается не ясен, пока кто-то не уточнит намерения автора.
Компьютеры сталкиваются с такой же проблемой. Они хранят данные, а не абстрактные понятия, используя при этом 1 и 0. Позднее они считывают эти 1 и 0 и пытаются воссоздать абстрактные понятия из набора данных. В зависимости от сделанных допущений, эти 1 и 0 могут иметь абсолютно разное значение.
Почему так происходит? Ну, вообще-то нет такого правила, что компьютеры должны использовать один и тот же язык, так же, как нет такого правила и для людей. Каждый компьютер одного типа имеет внутреннюю совместимость (он может считывать свои собственные данные), но нет никакой гарантии, как именно интерпретирует эти данные компьютер другого типа.
Храним числа как данные
Так в чем же проблема — компьютеры отлично ладят с одиночными байтами, правда? Ну, все превосходно для однобайтных данных, таких как 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 (если оно беззнаковое). Давайте используем его в примере.
Теперь Вы видите проблему? Машина с порядком хранения от старшего к младшему считает, что s = 0x1234, в то время как машина с порядком хранения от младшего к старшему думает, что s = 0x3412. Абсолютно одинаковые данные дают в результате два совершенно разных числа.
И еще один пример
Давайте для “веселья” рассмотрим еще один пример с 4 байтовым целым:
Проблема 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), что очень здорово, если Вам необходима такая проверка. Машины с порядком от старшего к младшему хранят данные в памяти в привычном для человека виде (слева направо), что упрощает низкоуровневую отладку.
Так почему же все просто не договорятся об использовании одной из систем? Почему одни компьютеры пытаются быть отличными от других? Позвольте мне ответить вопросом на вопрос: почему не все люди говорят на одном языке? Почему в некоторых языках письменность слева направо, а у других справа налево?
Иногда системы развиваются независимо, а в последствии нуждаются во взаимодействии.