Автор Анна Евкова
Преподаватель который помогает студентам и школьникам в учёбе.

Разработка модуля для выполнения операций с натуральными числами в шестнадцатеричной системе счисления

Содержание:

Введение

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

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

Далее необходимо определить задачи, стоящие перед программным модулем, предъявляемую к нему функциональную составляющую и определиться с принципом его работы, на основании которого составить алгоритм работы программы и оформить его по ГОСТ 19.701-90.

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

Целью работы является разработка модуля для выполнения операций с натуральными числами в шестнадцатеричной системе счисления.

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

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

1. Обоснование актуальности разработки модуля

1.1. Шестнадцатеричная система счисления и её роль
в информационных технологиях

Система счисления — это символический метод записи чисел. Другими словами, это представление чисел с помощью письменных знаков. Каждая система счисления даёт числу уникальное представление[1].

Для отражения алгебраической и арифметической структуры чисел лучше других подходят позиционные системы счисления. В позиционных системах счисления один и тот же числовой знак (цифра) в записи числа имеет различные значения в зависимости от того места (разряда), где он расположен. Вводится понятие основания системы счисления — количество различных знаков, которые используются для записи цифр. Если максимально большой цифры не хватает для записи числа — задействуется следующий разряд. Разряд — это позиция цифры в числе, и нумеруются разряды справа налево. Каждый следующий разряд больше другого на степень основания системы счисления. Таким образом, для позиционной системы счисления с основанием b:

1234b = 1 * b3 + 2 * b2 + 3 * b1 + 4 * b0

К числу таких систем относится современная десятичная система счисления, возникновение которой связано со счётом на пальцах. С древних времён и по наши дни люди повсеместно используют десятичную систему счисления для произведения вычислений над любыми числами[1]. Однако, с развитием вычислительной техники старые устои приходится пересматривать. Дело в том, что в вычислительной технике каждая цифра должна быть как-то представлена на физическом носителе. Если применять десятичную систему счисления на вычислительной технике, то придётся создавать такие устройства, которые могут находиться в десяти различных устойчивых состояниях. Это технологически сложно. Проще изготовить физический элемент, который может удерживать лишь два состояния (например - есть ток и нет тока). Это одна из основных причин, почему именно двоичная система счисления является языком вычислительной техники[2].

В двоичной системе счисления используется всего две цифры — 0 и 1. Другими словами, это позиционная система счисления с основанием 2. Поскольку в формировании числа участвуют всего две цифры, разряды достигают своего предела быстрее, чем в других системах счисления, а это приводит к тому, что двоичное представление числа почти всегда будет длиннее, чем десятичное[1].

Устройство, способное находиться в одном из двух устойчивых положений называется триггером, а каждый разряд двоичной формы записи числа — битом. Восемь битов объединяют в один байт, а байты, в свою очередь, в слова (одно слово — два байта) и в двойные слова (четыре байта). Именно двойное слово является основной единицей хранения информации в вычислительных системах на базе процессоров архитектуры x86, самой популярной архитектуры процессоров[2]. Такие числа имеют тридцать два символа и состоят сплошь из нулей и единиц и для человека работа с такими числами является не вполне удобной. Во-первых, из-за длины представления чисел, а во-вторых, постоянное повторение и чередование ноликов и единиц нередко вызывает ошибки. В результате, часто программисты используют другие системы счисления, совместимые с двоичной. Совместимыми являются те системы счисления, чьи основания являются степенью одного и того же числа[1].

Например, 2 = 21, а 8 = 23, то есть 2 и 8 являются степенью одного и того же числа. Проявляется это в том, что любое двоичное число можно представить в восьмеричной системе счисления, разбив все цифры на группы по три разряда (начиная от первого разряда). По три — потому что степень основания восьмеричной системы счисления в три раза больше, чем степень основания двоичной. Такие группы по три разряда называются триадами. Далее обращаемся к таблице триад, представленной на рисунке 1.

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

Восьмеричная
система
счисления

000

0

001

1

010

2

011

3

100

4

101

5

110

6

111

7

Рис. 1. Таблица триад

Подставленные на место триад восьмеричные цифры образуют восьмеричную форму числа. Например:

11011110012 = 001 101 111 0012 = 15718

Восьмеричная система счисления была весьма распространена на заре информатики, но имеет ряд недостатков, один из которых — неудобство представления двойных слов. Двойное слово состоит из тридцати двух разрядов, и не делится на триады нацело[3].

Во устранение данного недостатка стали использовать шестнадцатеричную систему счисления, основанием которой является двойка в четвёртой степени[2]. Перевод из двоичной системы счисления в шестнадцатеричную похож на перевод в восьмеричную систему, за исключением того, что двоичные разряды группируются по четыре (ведь степень основания шестнадцатеричной системы счисления превышает степень основания двоичной в четыре раза). Таким группы по четыре разряда называются тетрадами. В шестнадцатеричной системе счисления шестнадцать различных цифр, что на шесть больше, чем привычных нам арабских цифр. Для обозначения этих самых шести цифр, идущих вслед за арабскими, используются первые шесть букв латинского алфавита — A, B, C, D, E, F[1]. Для перевода чисел из двоичной системы счисления в шестнадцатеричную и наоборот используют таблицу тетрад, представленную на рисунке 2.

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

Шестнадцатеричная
система
счисления

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

Шестнадцатеричная
система
счисления

0000

0

1000

8

0001

1

1001

9

0010

2

1010

A

0011

3

1011

B

0100

4

1100

C

0101

5

1101

D

0110

6

1110

E

0111

7

1111

F

Рис. 2. Таблица тетрад

Пример представления двоичного числа в шестнадцатеричной системе счисления:

10111110111011112 = 1011 1110 1110 11112 = BEEF16

Неоспоримое преимущество шестнадцатеричной системы счисления в удобном выражении через неё двоичного кода. Один байт, равный восьми битам — это ровно две тетрады. Соответственно, слово — это четыре тетрады, а двойное слово — восемь[2].

В низкоуровневом программировании шестнадцатеричная система счисления играет очень важную роль. Дело в том, что регистры процессора архитектуры x86 представляют собой именно двойные слова. При этом регистры разбиты на слова, а слова - на байты, для организации удобного обращения к ним и выполнения арифметических и логических операций[4]. Схема регистра общего назначения — аккумулятора представлена
на рисунке 3.

EAX

AX

AH

AL

Рис. 3. Схема регистра EAX

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

Регистр AX представляет собой слово, то есть 16-х разрядное двоичное число. Соответственно, при записи в него двоичных данных используют четыре шестнадцатеричные цифры.

Регистры AH и AL представляют собой байты, то есть восемь двоичных разрядов. Запись в байт производится с помощью двух шестнадцатеричных цифр[2].

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

1.2. Операции над натуральными числами
в шестнадцатеричной системе счисления

Натуральные числа — числа, возникающие естественным образом при счёте. Последовательность всех натуральных чисел, расположенных в порядке их возрастания, называется натуральным рядом[1].

В первом случае ряд натуральных чисел начинается с единицы, во втором — с нуля. Не существует единого для большинства математиков мнения о предпочтительности первого или второго подхода (то есть считать ли ноль натуральным числом или нет). В подавляющем большинстве российских источников традиционно принят первый подход. Второй подход, например, применяется в трудах Бурбаки, где натуральные числа определяются как мощности конечных множеств. Кроме того, отсчёт с нуля широко распространён в программировании (например, для индексации массивов, нумерации битов, машинного слова)[3].

Для натуральных чисел естественным образом определены операции сложения и умножения. При объединении двух наборов, содержащих некоторое количество предметов, новый набор будет иметь столько предметов, сколько было в первых двух наборах вместе. Указанное действие носит название сложения и является простейшей бинарной операцией.

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

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

Обратной к операции умножения является операция деления. Первое определение деления — это поиск числа, которое содержится в делимом столько раз, сколько единиц содержится в делителе. Такое определение дано в учебниках арифметики XIV века. Для натуральных чисел, не являющихся множителем и произведением, известна операция деления с остатком.

Операция вычитания является ничем иным, чем операцией сложения с отрицательным числом. Умножение — это многократное сложение. Деление, самая сложная операция — многократное вычитание, а как известно, вычитание — и есть сложение. Любая из четырёх арифметических операций сводится к сложению. Арифметико-логическое устройство процессора физически умеет выполнять только сложение, однако, благодаря удобному представлению отрицательных чисел в двоичной системе счисления и алгоритмам, выполняет все четыре арифметические операции[2].

Выполнение арифметических операций над числами в шестнадцатеричной системе счисления производится по всем канонам позиционных систем счисления. Необходимо лишь помнить, что максимальной цифрой в каждом разряде является F[1].

Примеры выполнения операций над числами в шестнадцатеричной системе счисления:

  • 116 + 516 = 616;
  • 2916 + 116 = 2A16;
  • 10016 - 116 = FF16;
  • 1016 * 1016 = 10016;
  • A16 * B16 = 6E16;
  • DEAD16 / 516 = 2C8916

1.3. Достоинства и недостатки модульного программирования

Модульное программирование — это организация программы как совокупности небольших независимых блоков, называемых модулями, структура и поведение которых подчиняются определенным правилам. Использование модульного программирования позволяет упростить тестирование программы и обнаружение ошибок. Аппаратно-зависимые подзадачи могут быть строго отделены от других подзадач, что улучшает мобильность создаваемых программ[3].

Модуль — функционально законченный фрагмент программы. Во многих языках (но далеко не обязательно) оформляется в виде отдельного файла с исходным кодом или поименованной непрерывной её части. Некоторые языки предусматривают объединение модулей в пакеты.

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

Второй концепцией является аксиома модульности Коуэна. Модуль — независимая программная единица, служащая для выполнения некоторой определённой функции программы и для связи с остальной частью программы. Программная единица должна удовлетворять следующим условиям:

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

Третьей концепцией модульного программирования является сборочное программирование Цейтина, согласно которой модули — это программные кирпичи, из которых строится программа[3].

Существуют три основные предпосылки к модульному программированию:

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

Функциональная спецификация модуля должна включать:

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

Существуют три основные разновидности модулей:

  1. Функциональные модули, реализующие, как правило, одну какую-либо определенную функцию. Основным и простейшим модулем практически во всех языках программирования является процедура или функция.
  2. Информационные модули, реализующие, как правило, несколько операций или функций над одной и той же структурой данных (информационным объектом), которая считается неизвестной вне этого модуля.
  3. Логические модули, объединяющие набор функциональных и/или информационных модулей.

Как можно заметить, модульное программирование призвано упростить разработку приложений, взамен скрывая от разработчика истинные процессы, происходящие в модуле. Сам модуль выступает в роли "черного ящика" — при его вызове мы знаем только какие данные ему необходимо передать и чем будет являться ответ.

Модуль может являться как функцией (процедурой) в функциональном (процедурном) языке программирования, как классом в объектно-ориентированном языке программирования, так и отдельной независимой программой, работающей под управлением программной оболочки[3].

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

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

2. Разработка алгоритма программного модуля

2.1. Задачи, решаемые программным модулем

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

  1. Быть доступным для подключения к программной оболочке.
  2. Принимать строковые запросы от оболочки.
  3. Производить вычисления, в соответствии с принятым запросом.
  4. Отправлять оболочке результаты вычислений.

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

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

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

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

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

2.2. Функциональная составляющая модуля и принцип его работы

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

Стандартные потоки ввода-вывода — потоки процесса, имеющие номер (дескриптор), зарезервированный для выполнения некоторых «стандартных» функций. Как правило (хотя и не обязательно), эти дескрипторы открыты уже в момент запуска задачи (исполняемого файла)[4].

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

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

Таким образом, при отладке созданного модуля возможно будет пользоваться командной строкой Windows, посылая запросы и получая ответы через терминал. При работе модуля под управлением программной оболочки стандартные потоки ввода и вывода будут перенаправлены в процесс программной оболочки.

Запросы, допустимые к обработке модулем, должны быть строго стандартизированы. Именно поэтому возникает необходимость в разработке протокола запросов и ответов.

Протокол передачи данных — стандарт, описывающий правила взаимодействия функциональных блоков при передаче данных[3].

Согласно задачам, решаемым программным модулем, модуль должен принимать строковые запросы, ведь шестнадцатеричное представление натурального числа — ни что иное, как строка.

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

Идентификаторы операций могут быть представлены любыми символами, но интуитивно понятные обозначения арифметических операций — это символы "+", "-", "*" и "/". Кроме того, ни один из этих символов не является шестнадцатеричной цифрой.

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

Разумным решением будет оформить запрос в следующем виде:

[первое число][идентификатор операции][второе число]

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

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

2.3. Алгоритм программного модуля

На основе задач, решаемых программным модулем и возлагаемых на него функций был составлен алгоритм, блок-схема которого
отображена в приложении 1. Блок-схема выполнена на основании
ГОСТ 19.701-90 "Схемы алгоритмов, программ, данных и систем. Условные обозначения и правила выполнения"[3].

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

Далее, модуль для выполнения операций с натуральными числами в шестнадцатеричной системе счисления ожидает от программной оболочки передачи данных по разработанному протоколу. Программная оболочка передаёт модулю строку, содержащую информацию как о значении двух шестнадцатеричных числах, над которыми будет производиться операция, так и о том, какая операция была выбрана. Для приёма данных используется стандартный поток ввода, известный также, как поток номер 0[4].

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

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

Получившийся результат немедленно передаётся программной оболочке в виде строки, содержащей шестнадцатеричное число. Для передачи данных используется поток номер 1, являющийся стандартным потоком вывода[4].

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

3. Реализация разработанного алгоритма

3.1. Выбор языка программирования

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

Из алгоритма программного модуля видно, что программа не является сложной, а значит, будет разумно реализовать её на языке программирования низкого уровня. Низкоуровневый язык программирования (язык программирования низкого уровня) — язык программирования, близкий к программированию непосредственно в машинных кодах используемого реального или виртуального процессора. Для обозначения машинных команд обычно применяется мнемоническое обозначение. Это позволяет запоминать команды не в виде последовательности двоичных нулей и единиц, а в виде осмысленных сокращений слов человеческого языка (обычно английских)[5].

Общеизвестный пример низкоуровневого языка — язык ассемблера, хотя правильнее говорить о группе языков ассемблера. Более того, для одного и того же процессора существует несколько видов языка ассемблера. Они совпадают в машинных командах, но различаются набором дополнительных функций (директив и макросов)[4].

Разрабатывая программный модуль для программных оболочек, работающих под управлением операционных систем семейства Windows, я выбрал язык ассемблера MASM.

Macro Assembler (MASM) — ассемблер для процессоров семейства x86. Первоначально был произведён компанией Microsoft для написания программ в операционной системе MS-DOS и был в течение некоторого времени самым популярным ассемблером, доступным для неё. Это поддерживало широкое разнообразие макросредств и структурированность программных идиом, включая конструкции высокого уровня для повторов, вызовов процедур и чередований[5].

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

3.2. Методика и средства реализации алгоритма
на языке программирования.

Для реализации алгоритма на языке ассемблера MASM, в первую очередь, необходимо задать используемый набор операций. В тексте программы задан набор операций для процессора 80586 директивой ".586"[4].

Следующим шагом необходимо определить модель памяти программного модуля. Под ОС Windows 32/64 есть только одна — плоская модель. Кроме того, директива ".model" задаёт порядок передачи параметров функциям, а так же, кто уравнивает стек, после того, как функция была вызвана. Целиком директива будет иметь вид ".model flat, stdcall"[4].

Для работы со стандартными потоками ввода/вывода будут использоваться такие функции, как "crt_scanf" и "crt_printf". Для обеспечения доступа к этим функциям в коде программы следует подключить библиотеку и заголовочный файл. Заголовочный файл для этих функций называется "msvcrt.inc" и подключается директивой "include". Соответственно, библиотека называется "msvcrt.lib" и подключается директивой "includelib". При подключении любых файлов необходимо так же указать и путь по них[5].

Функция "crt_scanf", в качестве параметров, получает форматную строку и неограниченное число переменных. Форматная строка находится в сегменте данных и имеет вид "%x%c%x", что значит — шестнадцатеричное число, символ, шестнадцатеричное число. В качестве переменных используются два двойных слова под шестнадцатеричные числа и один байт под символ.

В коде программы функции будут вызываться директивой "invoke". Данная директива является упрощённым средством вызова функций на языке макро ассемблера. Суть её заключается в том, что компилятор сам добавит необходимое количество записей параметров в стек (вместо нескольких команд "push"), и вызовет функцию вместо команды "call"[5].

После вызова функции и поступления запроса в поток стандартного ввода все переменные будут иметь свои значения. На этом этапе следует распознать, какая именно арифметическая операция была задана. Из разработанного протокола видно, что операция задаётся символом — "+", "-", "/" или "*". Каждый из этих символов имеет код, и что бы узнать заданную операцию, достаточно сравнить код символа в переменной с заранее определёнными константами.

Сравнение проводится с помощью команды "cmp". Данная команда присваивает флагу нуля в регистре флагов значение "1", если разность сравниваемых чисел будет равна нулю (при этом сама разность никуда не записывается). Команда условного перехода "je" осуществляет передачу управления идущей за ней метке, если флаг нуля равен единице[4]. Таким образом, сравниваем значение символьной переменной с каждым значением символа операции и осуществляем затем условный переход на метку осуществления данной операции. Если ни одна из констант не сравнялась со значением символьной переменной, то налицо нарушение протокола обмена данными и программа будет завершена командой "ret", дабы освободиться для работы под управлением других оболочек.

Команда "add" осуществляет сложение двух операндов. Результат сложения помещается в первый операнд (приёмник). Например, при выполнении команды "add eax, num2" к содержимому регистра EAX прибавляется содержимое переменной num2. После выполнения команды результат будет находиться в регистре EAX. Первый операнд может быть регистром или переменной. Второй операнд (источник) — регистром, переменной или числом[4]. Невозможно, однако, осуществлять операцию сложения одновременно над двумя переменными, поэтому значение переменной num1 должно быть перенесено в регистр EAX заранее.

Команда "sub" вычитает из левого операнда правый. Левый операнд может быть регистром или переменной, а правый — регистром, переменной или числовой константой.

Единственным операндом команды "mul" может быть регистр или переменная. Здесь важен размер операнда (источника). Если операнд будет иметь длину в четыре байта, то он будет умножаться на значение регистра EAX, а результат будет помещён в пару регистров EDX:EAX[4].

Деление натуральных чисел осуществляется с помощью команды "div". Команда имеет всего один операнд — это делитель. Делитель может быть регистром или переменной. В зависимости от размера делителя выбирается и делимое. Если делитель имеет размер в четыре байта, делимое будет находиться в паре регистров EDX:EAX. После выполнения операции частное от деления окажется в регистре EAX, а остаток от деления — в EDX[4].

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

Форматная строка для вывода данных с помощью функции "crt_printf" находится так же в сегменте данных модуля и имеет вид "%x", то есть одно шестнадцатеричное число[5]. Результат передаётся в стандартный поток вывода с помощью вызова функции "crt_printf", после чего программа будет завершена с помощью команды "ret".

Программный код модуля для выполнения операций с натуральными числами в шестнадцатеричной системе счисления представлен в приложении 2.

3.3. Компиляция, испытание и анализ разработанного программного модуля

Что бы из кода получить саму программу, его необходимо скомпилировать. Для компиляции модуля для выполнения операций с натуральными числами в шестнадцатеричной системе счисления необходимы программы "ml.exe" и "link.exe" из пакета MASM32[4]. Команды для компиляции модуля из файла с кодом "hexprocessor.asm":

ml /c /coff hexprocessor.asm

link /subsystem:windows hexprocessor.obj

Готовая программа "hexprocessor.exe" занимает 2,5 Кб в памяти компьютера, а по факту 4 Кб в операционных системах семейства Windows, имеющих файловую систему NTFS. В любом случае, это значение меньше требуемого минимума и по данной характеристики никаких нареканий нет.

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

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

Заключение

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

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

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

В работе проведён детальный разбор средств языка ассемблера MASM32, позволяющих реализовать разработанный алгоритм. Текст готовой программы (как и алгоритм), приведены в приложениях.

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

Список использованных источников

  1. Гашков С. Б. Системы счисления и их применение. — М.: МЦНМО, 2004, — 52 с.
  2. Таненбаум Э. Архитектура компьютера. 5-е изд. — СПб.: Питер, 2007, — 844 с.
  3. Дуванов А. А., Рудь А. В., Семенко В. П. Азы программирования. Факультативный курс. Книга для учителя. — СПб.: БХВ-Петербург, 2005. — 496 с.
  4. Пирогов В. Ю. Ассемблер на примерах. — СПб.: БХВ-Петербург, 2012. — 416 с.
  5. Пирогов В. Ю. Ассемблер для Windows. Изд. 4-е перераб. и доп. — СПб.: БХВ-Петербург, 2012. — 896 с.

Приложения

Приложение 1. Алгоритм программного модуля для выполнения операций
с натуральными числами в шестнадцатеричной системе счисления

Получение от оболочки данных о числах и операции

Выбор операции

Передача ответа программной оболочке

Выполнение операции "деление"

Выполнение операции "умножение"

Выполнение операции "вычитание"

Выполнение операции "сложение"

Приложение 2. Текст программного модуля на языке ассемблера

.586

.model flat, stdcall

include \masm32\include\msvcrt.inc

includelib \masm32\lib\msvcrt.lib

.data

scanForm db "%x%c%x",0

printForm db "%x",0

.data?

num1 dd ?

num2 dd ?

symb db ?

.code

start:

invoke crt_scanf, offset scanForm, offset num1, offset symb, offset num2

mov eax, num1

cmp symb, 43

je addi

cmp symb, 45

je subs

cmp symb, 42

je mult

cmp symb, 47

je divi

ret

addi:

add eax, num2

jmp finish

subs:

sub eax, num2

jmp finish

mult:

mul num2

jmp finish

divi:

xor edx,edx

div num2

finish:

invoke crt_printf, offset printForm, eax

ret

end start