====== Лабораторная работа № 8 ====== ====== Введение в программирование на С/С++ с применением ассемблерных вставок. Первая программа на языке C/C++. ====== === 1. Запускаем Microsoft Visual Studio 2010, выбираем Файл – Создать – Проект… : === {{:workroom:inb31-2013:comp:index:lab8_1.png}} Рисунок 1. === 2. В открывшемся окне выбираем Другие языки – Visual C++ - Win32 – Консольное приложение Win32 === Проекту необходимо задать имя и указать расположение. Выбранный тип проекта позволяет создавать приложение-«обертку» для нашего ассемблерного кода, используя только API-функции Windows. {{:workroom:inb31-2013:comp:index:lab8_2.png}} Рисунок 2. === 3. В открывшемся диалоговом окне необходимо нажать Далее, затем выбрать тип приложения Консольное приложение и отметить галочку Пустой проект, затем нажать Готово. === {{:workroom:inb31-2013:comp:index:lab8_3.png}} Рисунок 3. {{:workroom:inb31-2013:comp:index:lab8_4.png}} Рисунок 4. === 4. После того, как проект создан, в Обозревателе решений выбираем Файлы исходного кода – Добавить – Создать элемент … (Обозреватель решений доступен во вкладке Вид). === {{:workroom:inb31-2013:comp:index:lab8_5.png}} Рисунок 5. === 5. В открывшемся диалоговом окне выбираем Файл С++(.cpp). Поскольку мы предполагаем использовать лишь один файл в нашем проекте, назовем его также, как и проект. === {{:workroom:inb31-2013:comp:index:lab8_6.png}} Рисунок 6. === 6. Напишем элементарную программу. === /* подключаемые заголовочные файлы */ #include // необходим для работы printf #include // необходим для работы _getch(); /* объявления функций */ int add(int, int); // складывает два целых числа int sub(int, int); // вычитает из первого целого второе int prov(int, int); // в случае, если первое число больше второго, выполняет sub, // иначе выполняет add /* глобальные переменные */ int i1, i2; void main() // основная функция. Тип void означает, что эта функция ничего // не возвращает { i1 = 10; // объявляем локальные переменные i2 = 20; printf("%d\n", prov(i1, i2)); // выводим результат функции prov // запись в кавычках определяет формат вывода: // %d означает, что будет выведено целое число, // \n означает "конец строки" _getch(); // ждет ввода любого символа с клавиатуры и возвращает его, // используется для того, чтобы консоль не закрывалась после выполнения // программы в режиме отладки } /* реализация функций */ int prov(int a, int b) { int res; if (a>b) res = sub(a, b); else res = add(a, b); return res; } int add(int a, int b) { return a+b; } int sub(int a, int b) { return a-b; } Функция **printf** будет использоваться нами постоянно, поэтому есть смысл сказать о ней несколько слов. Она является стандартной библиотечной функцией языка Си и выводит информацию на консольное устройство (текстовый экран). В общем случае формат этой функции можно представить так: printf(формат, список переменных) Параметр формат представляет собой строку, ограниченную кавычками, в которой, в частности, должны указываться типы переменных, содержащиеся в параметре список переменных. Строка printf("%d", a) означает, что будет выведено значение переменной a, причем тип задан как %d , т. е. целый со знаком и 32-битный по умолчанию. Кроме %d можно также использовать %u - целый без знака, %x - 32-битное число в шестнадцатеричной системе счисления, %s – строка, %d – тип double. Типы в формате должны соответствовать переменным в списке. В строке формата можно указывать комментарий, который будет выведен вместе со значениями: printf("%d больше чем %d", a, b) === 7. Выберем Отладка – Начать отладку. === Очевидно, что результат работы программы 30. {{:workroom:inb31-2013:comp:index:lab8_7.png}} Рисунок 7. Попробуйте закомментировать строки #include и _getch() и запустить программу в режиме отладки. Консоль закроется сразу после выполнения программы. Теперь попробуйте выбрать Запуск без отладки. В этом случае будем выведено сообщение **Для продолжения нажмите любую клавишу… **. === Начало программирования на ассемблере === Для использования команд ассемблера в программах на языке С применяется ключевое слово __asm. Есть два способа использовать это ключевое слово в программе: употреблять __asm для каждой команды процессора или использовать фигурные скобки для обозначения блока ассемблерных команд. Таким образом, фрагмент __asm { MOV EAX, EDX; OR EAX, EBX; } полностью эквивалентен фрагменту __asm MOV EAX, EDX; __asm OR EAX, EBX; === Пример простейшей программы на Си с использованием ассемблерной вставки: === /* подключаемые заголовочные файлы */ #include // необходим для работы printf #include // необходим для работы _getch(); /* глобальные переменные */ int a; /* главная функция */ void main() { a=10; __asm { ADD a, 10; // прибавляем к а 10 SUB a, 2; // вычитаем из a 2 }; printf("%d ",a); _getch(); } === Кратко о регистрах === Микропроцессоры Intel включают регистры общего назначения, регистр флагов, сегментные регистры, управляющие регистры, системные адресные регистры, а также отладочные регистры. Особо следует отметить регистр EIP, который называют указателем команд. В нем всегда содержится адрес команды относительно начала сегмента. К данному регистру нет прямого доступа, но косвенно многие команды изменяют его содержимое (команды передачи управления). Рассмотрим **рабочие регистры**. Их надо использовать, когда очень важно быстро обратиться к данным. По возможности следует так писать программы, чтобы большую часть вычислений проводить с загруженными один раз в эти регистры данными. ^ EAX ^^^ EBX ^^^ ECX ^^^ EDX ^^^ | E-часть | AX || E-часть | BX || E-часть | CX || E-часть | DX || | E-часть | AH | AL | E-часть | BH | BL | E-часть | CH | CL | E-часть | DH | DL | | EAX (сокращение от Accumulator) | EAX=16+AX=16+AH+AL | | EBX (сокращение от Base) | EBX=16+BX=16+BH+BL | | ECX (сокращение от Counter) | ECX=16+CX=16+CH+CL | | EDX (сокращение от Data) | EDX=16+DX=16+DH+DL | Названия регистров можно писать и строчными буквами. EAX, EBX, ECX и EDX - 32-битные регистры данных центрального процессора (от 386-го и по сей день). В эти регистры помещаются необходимые значения, и в них же чаще всего оказываются результаты вычислений. В 32 битах можно хранить hex-число от 00 00 00 00h до FF FF FF FFh. И это всё, что может там храниться. На назначения (Accumulator, Base, Counter, Data) пока можно не обращать внимания. Для записи числа в регистр, в переменную или в память используется оператор MOV: === Команда MOV === | Происхождение | от англ. слова move - движение, перемена места | | Формат | mov приемник, источник | | Действие | Копирует содержимое источника в приёмник | | Примечание | MOV не может передавать данные между двумя адресами оперативной памяти (для этой цели существуют команды MOVS)| При использовании ассемблерных вставок с С++ в качестве одного из элементов (источника или приемника) может выступать переменная, объявленная средствами С++. Переписывать значение из одной переменной в другую нельзя. ** Пример ** mov ah,09 // поместить значение 9 в регистр AH mov dx,010D // поместить значение 010Dh в DX Получив первую инструкцию, процессор выполнит инициализацию своего 8-битного регистра AH значением 9, после чего регистр AH будет содержать только байт 09. При выполнении второй команды процессор поместит в свой 16-битный регистр DX значение 010Dh, после чего регистр DX будет содержать только эти два байта 01 и 0Dh. Важно понять следующее: так как регистр DX состоит из DH и DL, то можно сказать, что после выполнения второй строки кода программы в регистре DH окажется значение 01, а в регистре DL окажется значение 0Dh. | | DH | DL | |DX =|01| 0D| Важно! В 16-битный регистр (AX,BX,CX,DX) нельзя положить значение больше двух байт (FFFFh), а в 8-битный (AH,AL, BH,BL, CH,CL, DH,DL) нельзя положить больше байта, то есть FFh. Допустим: |EAX=|99884433| | AX=| 4433| | AH=| 44 | | AL=| 33| Важно понять, что физически есть только 4 байта (99 88 44 33h). По отдельности можно обращаться к AX за значением 4433h, или к AH за 44h, или к AL за 33h. Но 9988h находится в E-части, а у неё нет собственного имени, она не является подрегистром. Вы не можете прочитать или загрузить 2 старших байта такого регистра, не обратившись ко всему регистру. Пример: mov EAX, 0FFFFFFFFh // Так правильно, и EAX будет равен FFFFFFFF mov EAX, 01FFFFFFFFh // Так НЕправильно. Значение больше регистра, // данной операции быть не может mov EAX, 0 // Так правильно, и EAX станет равен 00000000 mov AX, 0FFFFh // Так правильно, и EAX будет равен 0000FFFF mov AX, 1FFFFh // Так НЕправильно. Значение больше регистра, // данной операции быть не может mov AX, 0 // Так правильно, и AX станет равен 0000 mov AH, 111h // Так НЕправильно. Значение больше регистра, // данной операции быть не может mov AL, 100h // Так НЕправильно. Значение больше регистра, // данной операции быть не может mov AL, 0BBh // Так правильно, и EAX станет равен 000000BB mov AH, AL // так правильно, и EAX станет равен 0000BBBB К старшей части EAX отдельно обращаться можно, например, при помощи команд сдвига битов (будет рассмотрено подробно позже): shl EAX,10h // Сделает теперь регистр EAX равным BBBB0000 shr EAX,10h // А эта команда сделает его обратно равным 0000BBBB === Арифметические операции === Для выполнения арифметических операций используются команды ADD (сложение двух чисел), INC (прибавление единицы), SUB (вычитание), DEC (вычитание из числа единицы), IMUL (умножение), DIV (деление). В этой работе рассмотрим команды сложения и вычитания. === Команда ADD === | Происхождение | от англ. слова add - прибавлять, присоединять | | Формат | add приёмник, источник | | Действие | приёмник = приёмник + источник | | Примечание | если нужно увеличить всего на один, лучше использовать команду INC | **Пример** add AH,AL // прибавить к AH содержимое AL === Команда SUB === | Происхождение | от англ. слова sub, subtraction - вычитание | | Формат | sub приёмник, источник | | Действие | приёмник = приёмник - источник | | Примечание | если нужно отнимать всего один, лучше использовать команду DEC | Пример sub AH,AL // вычесть из AH содержимое AL === Задание === - Найдите сумму чисел, находящихся в регистрах EAX, EBX, ECX, накапливая ее в регистре EDX. Содержимое регистров EAX, EBX, ECX не меняйте. - Найдите разность суммы чисел, находящихся в регистрах EAX, EBX, и числа из регистра ECX. Результат – в регистре EDX. Содержимое регистров EAX, EBX, ECX не меняйте. - Сложите два вектора с целочисленными координатами (a1,a2) и (b1,b2). - Найдите разность двух векторов с целочисленными координатами (a1,a2) и (b1,b2). === Литература === - Могилев, А. В. Практикум по информатике: Учеб. пособие для студ. высш. учеб. заведений / А. В. Могилев, Н. И. Пак, Е. К. Хеннер; под ред. Е. К. Хеннера. – 2 изд., стер. – М.: Академия, 2005. – 608 с. - Пирогов, В. Ю. Ассемблер на примерах. СПб.: БХВ-Петербург, 2005. – 4163) с. - Дневники чайника. [Электронный ресурс] – http://bitfry.narod.ru/. Дата обращения 06.03.2013. \ Назад: [[workroom:inb31-2013:comp:index:lab8]] {{tag>}}