Inc ax ;Инкремент числа-заполнителя
add SI, 2 ;Смещение в массиве к следующему слову
loop fill ;На метку fill (CX раз)
Повышение эффективности достигается за счет того, что команда занесения числа в элемент массива оказывается короче (так как в нее не входит адрес массива) и выполняется быстрее, так как этот адрес не надо каждый раз считывать из памяти. Базово-индексная адресация со смещением. Адресуется память (байт или слово). Относительный адрес операнда определяется как сумма содержимого двух регистров и смещения. Это способ адресации является развитием предыдущего. В нем используются те же пары регистров, но полученный с их помощью результирующий адрес можно еще сместить на значение указанной в команде константы. Как и в случае базово-индексной адресации, константа может представлять собой индекс (и тогда в одном из регистров должен содержаться базовый адрес памяти), но может быть и базовым адресом. В последнем случае регистры могут использоваться для хранения составляющих индекса. Приведем формальный пример рассматриваемого режима адресации. Пусть в сегменте данных определен массив из 24 байтов, в котором записаны коды латинских и русских символов верхнего ряда клавиатуры:
sims db "QWERTYUIOP{}'
db "ЙЦУКЕНПШЦЗХЪ'
Последовательность команд
mov BX,12 ;Число байтов в строке
mov SI, 6
mov DL,syms[BX][SI]
загрузит в регистр DL элемент с индексом 6 из второго ряда, т.е. код ASCII буквы Г. Тот же результат можно получить, загрузив в один из регистров не индекс, а адрес массива:
mov BX, off set sym
mov SI,6
mov DL, 12 [BX] [SI]
Пример программы, выводящей строку «Hello World!!», использующей различные способы адресации и функции ЯВУ С:
#include <dos.h>
#include <conio.h>
// объявляем переменную с кодом цвета
char color = 3;
void main() {
// очиства экрана
clrscr();
// начало ассемблерной вставки
asm {
// сохраняем содержимое используемых регистров в стеке
push ax ;
push es ;
// в сегментный регистр es - адрес начала видеопамяти текстового режима
mov ax, 0B800h ;
mov es, ax ;
// в ячейку памяти es:0 соответствующую первому знакоместу
// (левому верхнему углу текстового экрана) записываем код символа ‘H’
// применяя непосредственную адресацию,
// а в ячейку es:1 соответствующую атрибутам первого знакоместа
// записываем код атрибутов
mov byte ptr es:0, 'H' ; // код символа
mov byte ptr es:1, 1 ; // атрибуты изображения символа
// в ячейку памяти es:2 соответствующую второму знакоместу
// записываем код символа ‘e’ применяя непосредственную адресацию
mov al, 'e' ; //
mov es:2, al ;
// в ячейку памяти es:2 соответствующую атрибутам второго знакоместа
// записываем атрибуты символа ‘e’ применяя регистровую адресацию
mov al, 2 ;
mov es:3, al ;
mov bx, 4 ;
mov byte ptr es:[bx], 'l' ;
// в ячейку памяти es:[bx] соответствующую атрибутам третьего знакоместа
// записываем код цвета 3 применяя прямую адресацию
// указывая в команде mov al, color имя переменной
inc bx ;
mov al, color ;
mov es:[bx], al ;
mov byte ptr es:[bx]+1, 'l' ;
mov byte ptr es:[bx]+2, 4 ;
mov di, 3 ;
mov byte ptr es:[bx][di], 'o' ;
mov byte ptr es:[bx][di+1], 5 ;
mov si, 5 ;
mov word ptr es:[bx][si], ' ' ;
add si, 2 ;
mov ax, 0x0657 ;
// \/\/
// | |
// | +--- 57h – код символа 'W'
// +----- 06h – код атрибутов символа
mov es:[bx][si], ax ;
add si, 2 ;
mov word ptr es:[bx][si], 076Fh ;
// \/\/
// | |
// | +--- 6Fh – код символа 'o'
// +----- 07h – код атрибутов символа
// восстанавливаем содержимое использованных регистров
pop es ;
pop ax ;
}
pokeb(0xB800, 0x0010, 'r');
pokeb(0xB800, 0x0011, 8);
poke(0xB800, 0x0012, 0x096C);
// \/\/
// | |
// | +--- 6Ch – код символа 'l'
// +----- 09h – код атрибутов символа
unsigned char far *ch; // описываем указатель на байт
// устанавливаем его на адрес 0xB800:0x0014 – позицию символа ‘d’
ch = (unsigned char far *) MK_FP(0xB800, 0x0014);
// \ / \ / \ /
// --------\/-------- -\/- -\/-
// | | |
// | | +---- смещение соответствующее
// | | десятому знакоместу
// | | 0x0014 = 20, 20 / 2 = 10
// | |
// | +------------ сегментный адрес
// | видеопамяти
//
// +------------------------------ приведение типа void*
// (бестипового указателя)
// к типу unsigned char far *
*ch = 'd'; // помещаем в ячейку 0xB800:0x0014 код символа ‘d’
*(ch+1) = 10; // помещаем в ячейку 0xB800:0x0015 атрибуты символа ‘d’
unsigned int far *cha; // описываем указатель на слово
// устанавливаем его на адрес 0xB800:0x0016 – позицию символа ‘!’
cha = (unsigned int far *) MK_FP(0xB800, 0x0016);
*cha = 0x0921;
// \/\/
// | |
// | +--- 21h – код символа ‘!’
// +----- 09h – атрибуты символа ‘!’
// переходим к ячейке памяти для следующего знакоместа
cha++; // ОБРАТИТЕ ВНИМАНИЕ адрес cha увеличивается на 2
// т.е. на размер типа, на который указывает cha
// т.е. на sizeof(int)
*cha = 0x8921;
// \/\/
// | |
// | +--- 21h – код символа ‘!’
// +----- 89h – атрибуты символа ‘!’:
// 8 – код цвета фона
// 9 – код цвета символа
// для сравнения выводим символ ‘!’ с использованием функций
// библиотеки conio.h
gotoxy(14, 1); // переход к позиции 14 символа в 1 строке
textcolor(7); // установка цвета 7 (серый)
cprintf("!"); // вывод символа ‘!’
}
Просмотреть ассемблерный код, который генерирует компилятор С можно выполнив команду:
c:\BC\BIN>bcc -S -Ic:\BC\INCLUDE -Lc:\BC\LIB ll2.cpp
если компилятор ВС размещен в каталоге c:\BC\BIN .
II Изучение способов определения состава оборудования в MS-DOS
Составить на ЯВУ С программу получения сведений о составе оборудования ПЭВМ:
-
тип ПЭВМ;
-
дату издания BIOS;
-
размер ОЗУ (основной памяти);
-
наличие, количество и тип НГМД;
-
наличие математического сопроцессора;
-
текущий режим адаптера дисплея;
-
количество установленных адаптеров последовательного порта и их базовые адреса;
-
наличие манипулятора «джойстик»;
-
количество адаптеров параллельного порта и их базовые адреса.
Получение указанной информации реализовать всеми возможными способами: с использованием ассемблерных вставок, средств BIOS и DOS, функций и макросов ЯВУ С. Сравнить полученные результаты. Использовать структуры с битовыми полями.
Определение размера ОЗУ обращением к ячейкам CMOS реализовать на языке Ассемблера в виде внешней процедуры с использованием раздельной компиляции. Процедура должна выводить сообщение о начале и окончании своей работы, с использованием обращений к функциям ЯВУ С.