Видео в конце...
Чуть ранее мы рассмотрели его набор команд и познакомились с языком ассемблера. Двигаемся далее. В начале семидесятых в сфере вычислительной техники произошел ряд важных изменений. Программисты желали чтобы их программы не нужно было переписывать заново при переходе от одной модели компьютера к другой. При этом программы должны были быть быстрыми, такими как будто их написали в машинных кодах. И это еще не все. Программисты хотели общаться с компьютером на как можно более простом языке. Однако, с этим были небольшие сложности. В частности, регистр аккумулятор процессора не способен сохранить в себя результат операции, если двоичное представление числа гораздо больше размеров самого регистра. Два программиста Кен Томпсон и Дэннис Ритчи, работая в подразделении Bell Labs постарались решить эту задачу.
Они создали компилятор языка с простым синтаксисом, при этом результатом его работы является весьма производительная программа в машинном коде.
Типизированный язык высокого уровня.
Чтобы сообщить компилятору о разрядности чисел, с которыми будет идти работа предусмотрены типы данных. Вот некоторые из них.
Тип char.
Размер один байт или 8 бит. Он однозначно позволяет понять, что в этих 8 битах содержится дополнительный код числа. Диапазон чисел от отрицательного -128 до положительного 127. Другой однобайтовый тип это unsigned char. Те же 8 бит, однако однозначно понятно, что любая комбинация бит будет положительным числом. Это позволяет хранить в байте данных любое число от 0 до 255. Другие типы целочисленных данных построены по схожему принципу, но количество байт у них больше, поэтому диапазоны чисел гораздо более широкие.
Назвали этот язык латинской буквой С. Чтобы запустить программу после ее написания на этом языке, нужно подать ее текст на вход программы, называемой компилятор.
По аналогии с ассемблером будет произведена обработка текстовой информации и генерация машинных кодов, которые уже можно запустить на выполнение. Важно отметить, что
- Процесс компиляции долгий, потому что это не просто построчный перевод мнемоник в машинный код, а полный анализ текста программы, поиск ключевых слов-маркеров и др.
- Долгая компиляция совсем не влияет на скорость выполнения программы, потому что программа потом хранится и запускается в виде машинных кодов.
- Программа быстра почти так же, как если бы изначально была написана в машинных кодах. Компиляторы пишутся людьми и постоянно совершенствуются, но все равно зачастую в машинный код попадает немало лишнего, что немного замедляет работу.
Постановка задачи.
Пусть задачей программы является сложение 16-битных чисел. После компиляции исходного кода мы получаем машинный код. Интересно внимательнее рассмотреть содержимое результата компиляции. Однако, сперва разберемся с сутью проблемы сложения чисел при помощи арифметико-логического устройства с разрядностью меньшей, чем у слагаемых.
Как можно было заметить, слагаемые в двоичном виде занимают размер больше одного байта. Не зря под каждое выделен тип short. Каждое слагаемое А и В представлено в памяти данных как два байта по соседству.
Один из них хранит старшие биты числа, другой младшие. На схеме памяти данных первое слагаемое отмечено синими байтами, второе красными, под результат выделено два желтых байта. Наш процессор при помощи своего арифметико-логического устройства способен выполнить операцию максимум над 8 битами. Поэтому такой процессор называют 8 битным. Общепринятым решением является сложение младших половин слагаемых, потом старших. Но есть одна тонкость. В процессоре не существует линии бита переноса между суммой младших и старших байт. Эти операции вообще происходят в разное время. На помощь приходит бит переноса, который сохраняется в специальном регистре, называемым флагом переноса (CARRY).
Флаг переноса.
Регистр бита переноса, он же флаг переноса подключен к дешифратору команд и влияет на его работу. Этот флаг влияет на выполнение операции перехода на новую инструкцию. Переход может произойти, а может после этой инструкции вызовется следующая по очереди. Все зависит от состояния флага. В нашем случае команда осуществит переход на новый адрес инструкции если флаг переноса 0. Это также заложено в мнемонике команды. jnc это сокращение от Jump No Carry. Переход если нет переноса. На схеме не было показано, но бит переноса регистра сбрасывается в 0 после команды jnc. Рассмотрим подробнее блок схему алгоритма.
После сложения младших байт и сохранения результата флаг переноса либо хранит 1, либо 0. В том случае, если перенос состоялся, то явно один бит нужно будет добавить к старшим байтам. И если переноса не было, то шаг добавления единицы мы пропускаем. Переходим сразу к шагу сложения старших разрядов. После сохранения результата алгоритм заканчивает работу.
Сложение чисел большой разрядности.
Запустим программу и проследим ход ее выполнения.
Обратим внимание, что слагаемые в виде пар байт, старших и младших уже помещены в память данных. Это было сделано за те шаги, которые мы пропустили для того чтоб не отвлекаться на лишнюю информацию. Как было описано в блок-схеме, складываем младшие байты слагаемых.
Для этого занесем в аккумулятор первый байт, сложим его со вторым. 8-битное арифметико-логическое устройство передало в аккумулятор число 3, что в совокупности с единичным битом переноса дает правильный ответ. Главное теперь этот бит переноса не потерять, а донести до старших байт. Сохраним сумму в младшем байте результата.
Команда jnc не будет осуществлять переход на метку next. Флаг переноса был равен 1.
Кроме того эта команда сбросила значение флага в ноль. Обратите внимание, что в машинном коде команды jnc в операнде адрес ячейки памяти программ равен 01111. По этому адресу находится инструкция lda 3, на которую мог быть переход. Метка next находится как раз перед этой инструкцией. Но перехода не произошло, по алгоритму нужно не потерять бит переноса. Чтоб это сделать необходимо добавить единицу к результату суммы старших бит.
Единица хранится в ячейке 1, добавляем ее в аккумулятор.
Сохраняем единицу в памяти, где должны лежать суммы старших байт.
Далее обычным образом складываем старшие байты, добавляя их к содержимому аккумулятора.
Сохраняем результат и останавливаем работу программы.
Сложение чисел малой разрядности.
Теперь посмотрим как отработает программа если разрядность суммы младших байт не превышает разрядность аккумулятора. Складываем числа 7 и 8.
При сложении бит переноса 0. Сохраним результат.
Команда jnc произведет переход на инструкцию с адресом 01111, поскольку бит переноса 0.
А там далее сложение старших байт, в этом примере они еще и нулевые, так что ничего интересного не произойдет.
Выводы.
Компилятор.
Знакомство с языком С оказалось крайне коротким. В основном, был показан принцип работы компилятора и тем самым уже не допущено важнейшее недопонимание. Процессор запускает не исходный код на языке С, а машинный код, который генерирует компилятор языка. Компилятор сам за нас решил проблему сложения чисел, разрядность которых превышает разрядность арифметико-логического устройства. Мы ему подсказали всего лишь указав тип данных, с которыми будет идти работа.
Команда условного перехода.
Это, пожалуй, одна из важнейших команд процессора, позволяющая организовать выполнение ветвей алгоритма по условию, а также организовать циклы. О них мы поговорим в другой раз. Еще этот язык отличается тем, что жив по меркам компьютерной эры уже целую вечность. Это без малого 50 лет. Он прост в изучении, потому что его синтаксис крайне консервативен. Мощнейшим средством языка является указатель, о котором мы также поговорим позднее. Достоинство языка стало и его недостатком. Использование указателей требует дисциплины, внимания и очень хорошего представления процессов, происходящих в памяти компьютера.