Немонотонное изменение параметра цикла
Пусть индекс изменяется по произвольному закону. В этом случае сначала вычисляется значение индекса, а затем выполняются действия для элемента.
Пример 1. Составить программу для нахождения заданного элемента в массиве целых упорядоченных чисел.
nom=-1;
i=0; j=n-1;
while (i<=j){
k=(i+i)/2;
if (x[k]==r){
nom = k;
break;
}
if (x[k]<r)i=k+1; else j=k-1;
}
;nom=-1;
mov eax, -1
;i=0; j=n-1;
mov ebx, 0
mov ecx, [n]
dec ecx
;while (i<=j){
cmp ebx, ecx
jg short m1
; k=(i+i)/2;
mm9:
mov edx, ebx
add edx, ecx
shr edx, 1
; if (x[k]==r){
mov esi, [x+edx*4]
cmp esi, [r]
jne mm6
;nom = k;
mov [nom], edx
;break;
jmp short mm7
;}
;if (x[k]<r)i=k+1; else j=k-1;
mm6:
cmp esi, [r]
jge short mm8
mov ebx, edx
inc ebx
jmp short mm9
mm8:
mov ecx, edx
dec ecx
jmp short mm9
;}
Пример 2. В заданной строке символов переставить символы в порядке, заданном числовым массивом
for (i=0; str1[i]; i++){
ch = str1[i];
n = nom[i];
str2[n] = ch;
}
str2[i] = 0;
str1 db ‘abcdef’, 0
nom dd 5, 1, 0, 2, 4, 3
str2 db (nom - str1) dup (?)
...
;for (i=0; str1[i]; i++){
mov eax, 0
;ch = str1[i];
m2:
mov bl, [str1+eax]
test bl, bl
je short m1
;n = nom[i];
mov ecx, [nom+eax]
;n = nom[i];
mov [str2+ecx], bl
inc eax
jmp m2
m1:
Использование команд для работы с блоками
Команды позволяют обрабатывать массив чисел длиной 1, 2, 4 байтов, начиная с начала или конца массива. При этом автоматически изменяется адрес элемента массива на длину элемента массива. Для массива можно выполнить следующие операции:
копирование массива в другой массив:
сравнение двух массивов;
заполнение массива заданным значением или последовательным чтением элементов массива;
поиск заданного элемента массива.
Для определения направления поиска устанавливается флаг d регистра флагов (бит № 10).
Адрес исходного массива задается в регистре ESI, а результирующего - в регистре EDI.
Количество элементов массива задается в регистре ECX.
Команды :
MOVS{B|W|D} - do{*(edi++) = *(esi++) while (ecx--);}
LODS{B|W|D} {AL|AX|EAX} = *(esi++)
STOS{B|W|D} *(edi++) = {AL|AX|EAX}
CMPS{B|W|D} сравнение *(esi++) и *(edi++)
SCAS{B|W|D} do{ if {AL|AX|EAX} = (!=) ) *(edi++) break; while (ecx--);
В таблице 7.1 представлена сводная таблица команд для работы с блоками
Таблица 7.1. Сводная таблица команд для работы с блоками
Код | Выполняемые действия | Подготовка команды | Используемый префикс | ||||
MOVS | dest = src | cld (std);
esi = &src[10] edi = & dest ecx = number | REP | ||||
LODS | {al, ax, eax} = *esi++ | cld (std);
esi = &src | Префикс не используется | ||||
STOS | *edi++ = {al, ax, eax} | cld (std);
edi = &dest ecx = number | REP | ||||
CMPS | *src ++
<>*dest++ | cld (std);
esi = &src7 edi = & dest ecx = number | REPE, REPNE | ||||
SCAS | {al, ax, eax} <>
*dest++ | EAX - что ищем,
edi = & dest (где ищем) ecx = number | REPE, REPNE |
Примеры использования команд
Скопировать область памяти заданной длины в другую область памяти, если адрес начала первой области f1, ее размер len байт, адрес начала второй области f2. Пусть len кратно 4.
В этой программе сначала определяется направление копирования. Если области памяти для исходного и результирующего массивов пересекаются, при этом адрес начала результирующего массива больше, чем адрес исходного, необходимо копировать, начиная с конца массива, в противном случае – с начала.
Е
Movаааа esi, [f1]
Movаааа edi, [f2]
Movаааа ecx, [len]
Shrаааааа ecx, 2
Jecxzааа m1
cld
cmpаааа esi, edi
jaаааааааа short m2
jeаааааааа break
movаааа esi, eax
decаааааа esi
addааааа edi, [len]
decаааааа edi
std
m2:
repаааааа movsd
break:
cld
¦ЁшьхЁ 2. T чрфрээющ ёЄЁюъх ьрыхэ№ъшх ырЄшэёъшх сєътv чрьхэшЄ№ сюы№°шьш.
Str1ааааа dbааааааа СThis is stringТ,0
Е
cld
movаааа esi, offset str1
movаааа edi, offset str1
for1:
lodsb
testаааааа al, al
jeаааааааа short break
cmpаааа al, СaТ
jbаааааааа notl
cmpаааа al, СzТ
jaаааааааа notl
addааааа al, СAТ Ц СaТ
notl:
stosb
jmp for1
break:
cld
¦ЁшьхЁ 3. TюёЄртшЄ№ яЁюуЁрььє фы ёЁртэхэш фтєї фышээvї ўшёхы юфшэръютющ фышэv. ¦ЁюуЁрььр фюыцэр ЇюЁьшЁютрЄ№ Ёхчєы№ЄрЄ >0, хёыш яхЁтюх ўшёыю сюы№°х тЄюЁюую, Ёртэю 0, хёыш ўшёыр Ёртэv ш <0, хёыш яхЁтюх ўшёыю ьхэ№°х тЄюЁюую. ¦єёЄ№ ўшёыю ёюёЄюшЄ шч n 32-сшЄэvї лЎшЇЁ¬ ш чряшёvтрхЄё , эрўшэр ё ьырф°шї ЎшЇЁ.
Xааааааааа ddааааааа Е, Е., Е.
Yааааааааа ddааааааа Е, Е, Е
Nаааааааа ddааааааа (Y Ц X)/4
Resааааа ddааааааа ?
Е
subаааааа eax, eax
movаааа ecx, [N]
leaаааааа esi , [X+ecx*4-4]
leaаааааа edi , [Y+ecx*4-4]
std
repeаааа cmpsd
jecxzааа m1
jbаааааааа letter
movаааа eax, 1
jmpааааа short m1
letter: movаааааа eax, -1
m1:
cld
ааааааааа
¦ЁшьхЁ 4.
T чрфрээюь ьрёёштх ўшёхы тёх эєыхтvх чэрўхэш чрьхэшЄ№ Ц1
While (){
аа If (хёЄ№ эєыхтюх чэрўхэшх)
аа ааа¦рьхэшЄ№;
аа Else
ааааа Break;
}
movаааа ecx, [n]
cld
subаааааа eax, eax
movаааа edi, offset x
for1:
repneаа scasd
jecxzааа break
movаааа [dword ptr edi-4], -1
jmpааааа for1
Задание двухмерного массива
Пусть необходимо задать матрицу, определенную в С:
int matr[][4]= {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
matr dd 1,2,3,4
dd 5,6,7,8
dd 9,10,11,12
Задание адреса элемента
Для адресации данных массива можно использовать 2 регистра индексный и базисный [3.4.3]. Для каждого индекса определяется шаг изменения адреса.
Например, для массива:
int x[M][N] шаг изменения адреса по второму индексу Hj равен sizeof (int) = 4, а для первого индекса Hi равен Hj * N. Если длина элемента совпадает со значением масштабного множителя (стандартная длина элемента -1, 2, 4, 8), то в индексный регистр записывается значение индекса, если не совпадает то - значение смещения.
Для задания адреса элемента X[i][j] со стандартной длиной элемента используется запись вида [X+REG1 + REG2 * LEN], где REG1- общий регистр, содержащий смещение для i - ой строки, а REG2-
общий регистр, содержащий значение индекса j. Для элемента массива нестандартной длины адрес задается в виде [X+REG1 + REG2], где REG1- общий регистр, содержащий смещение для i - ой строки, а REG2- общий регистр, содержащий смещение для j - ого элемента строки.
Пример. Оттранслировать операторы:
int matr[][4]= {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
long double numbers [][4]= {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
// Формирование i, j
matr[i][j]=0; numbers[i][j]=0;
matr dd 1,2,3,4
dd 5,6,7,8
dd 9,10,11,12
numbers
dt 1,2,3,4
dt 5,6,7,8
dt 9,10,11,12
HIMATR dd 16
HINumbers dd 40
...
mov eax, HIMATR
mul [i]
mov ebx, [j]
mov [matr+eax+ebx*4], 0
mov eax, HINumbers
mul [i]
mov ebx, eax; База
mov eax, 10
mul [j]
mov [dword ptr numbers + eax + ebx], 0
mov [dword ptr numbers + eax + ebx + 4], 0
mov [word ptr numbers + eax + ebx + 8], 0
Монотонное изменение индекса
Структура программы с монотонным изменением индексов:
Вычисление Hj , Hi
Цикл для вычисления.
Пример. Составить программу для вычисления сумм столбцов матрицы:
for (j=0; j<N; j++){
s[j]=0;
for (i=0; i<M; i++)
s[j]+=matr[i][j]
}
Для оптимизации программы запишем:
for (j=0; j<N; j++){
r=0;
for (i=0; i<M; i++)
r+=matr[i][j]
s[j]=r;
}
; Вычисление Hi =4*N, Hj=4
mov eax, [N]
shl eax, 2; Hi
mov ebx, 4; Hj
;for (j=0; j<N; j++){
mov ecx, [N]
shl ecx, 16
mov edx, 0; j
forj:
; r=0;
mov esi, 0
; for (i=0; i<M; i++)
mov cx, [M]
mov edi, 0; i
; r+=matr[i][j]
fori:
add esi, [matr + edi + edx*4]
; i++
add edi, eax
dec cx
jnz fori
; s[j]=r;
mov [s+edx*4], esi
;j++
inc edx
sub ecx, 10000h
jnz forj
}
...
Недостатки метода:
· для каждого индекса свой регистр;
· не может быть обобщен на массивы большей размерности.
Метод связанных индексов
Адрес элемента в начале строки используется для формирования адреса очередного элемента для обоих индексов, т.е. фактически требуется 2 экземпляра этого адреса. Т.к. фактически используется одно и тоже значение для работы с каждым индексом метод называется методом связанных индексов. Для получения 2-х экземпляров используется стек. Формируется необходимое значение, один экземпляр которого записывается в стек, второй остается в регистре для работы со вторым индексом. При необходимости работы с первым индексом извлекается значение из стека.
Структура программы для метода связных индексов:
for (i=0; i<.. i++){ mov ecx, ...
mov ebx, Смещение ПОЭ[11]
...
for (j=0; j<... ; j++){ fori:
... push ecx
push ebx
mov ecx,...
forj:
[Имя массива + ebx]
} add ebx, [Hj]
loop forj
} pop ebx
add ebx, Hi
pop ecx
loop fori
Пример 1. Составить программу для нахождения сумм строк матрицы с нечетными номерами
for (k=i=0; i<M; i+=2, k++){
r=0;
for (j=0; j<N; j++)
r+=matr[i][j]
s[k]=r;
}
mov esi, [N]
shl esi, 3; Шаг по i
;for (k=0. i=1; i<M; i+=2, k++){
sub eax, eax; k
mov ecx, [M]
shr ecx, 1
mov ebx, [N]
shl ebx, 2; i
fori:
;r=0;
subаааааа edx, edx
;аа for (j=0; j<N; j++)
pushаааа ecx
movаааа ecx, [N]
pushаааа ebx
forj:
;ааааа r+=matr[i][j]
addааааа edx, [matr+ebx]
;j++
addааааа ebx, 4
loopаааа forj
;аа s[k]=r;
movаааа [s+eax*4], edx
;i++
incаааааа eax
popааааа ebx
addааааа ebx, esi
popааааа ecx
loop fori
¦ЁшьхЁ 2. TюёЄртшЄ№ яЁюуЁрььє фы яхЁхьэюцхэш ьрЄЁшЎ
ideal
p586
model flat
extrn ExitProcess:proc
dataseg
matrаааааа ddааааа 1,1,1,1,1
аааааааааа ddааааа 2,2,2,2,2
аааааааааа ddааааа 3,3,3,3,3
аааааааааа ddааааа 4,4,4,4,4
Mааааааааа ddааааа 4
Nааааааааа ddааааа 5
Sааааааааа ddааааа 5 dup (?)
codeseg
begin:
movааа esi, [N]
shlааа esi, 3; ? г по i
;for (k=0. i=1; i<M; i+=2, k++){
subа eax, eax; k
movа ecx, [M]
shrа ecx, 1
movа ebx, [N]
shlа ebx, 2; i
fori:
;r=0;
subаа edx, edx
;аа for (j=0; j<N; j++)
pushааа ecx
movаааа ecx, [N]
pushааа ebx
forj:
;ааааа r+=matr[i][j]
addааа edx, [matr+ebx]
;j++
addа ebx, 4
loopааааа forj
;аа s[k]=r;
movаааааааа [S+eax*4], edx
;i++
incа eax
popа ebx
addа ebx, esi
popа ecx
loop fori
callааа ExitProcess
endаааа begin
¦ръ тшфэю шч яЁюуЁрььv фы ърцфюую шч ьрёёштют ЄЁхсєхЄё Єюы№ъю юфшэ ЁхушёЄЁ.
Использование 2-х мерного массива при немонотонном изменении индекса
Пусть задана разреженная матрица, содержащая очень много нулей.
Для упрощения задания определяются только ненулевые элементы матрицы в виде:
x, y, value,
где x, y - координаты ненулевого значения. Составить программу для заполнения матрицы заданными значениями.
Для решения этой задачи потребуется задать адрес элемента массива matr[x][y], который вычисляется по формуле:
& matr[x][y] = & matr[0][0] + x * Hx + y * Hy
; Задание матриц
PackMatr dd 1, 3, 10
dd 2, 7, 20
dd 4, 4, 30
count dd 3
UnpackMatr dd 5 dup (5 dup 0)
hi dd 5*4
; Заполнение матрицы UnpackMatr:
mov ecx, [count]
mov ebx, offset PackMatr
for1:
mov eax, [ebx] ;x
mul [hi] ; x * hi
mov edx, [ebx+4] ;y
mov esi, [ebx+8] ;value
mov [UnpackMatr+eax+edx*4], esi
add ebx, 12
loop for1
Использование метода связных индексов для многомерных массивов
Пример. Составить программу для реализации фрагмента программы:
int a[3][5][4];
for (k=1, l=0; k<4; k+=2, l++){
r=0;
for (i=0; i<3; i++)
for(j=0; j<5; j++)
r+=a[i][j][k];
s[l]=r;
}
...
dataseg
a dd 1,1,1,1
dd 2,2,2,2
dd 3,3,3,3
dd 4,4,4,4
dd 5,5,5,5
dd 1,1,1,1
dd 2,2,2,2
dd 3,3,3,3
dd 4,4,4,4
dd 5,5,5,5
dd 1,1,1,1
dd 2,2,2,2
dd 3,3,3,3
dd 4,4,4,4
dd 5,5,5,5
s dd 2 dup(?)
hk dd 4
hj dd 16
hi dd 16*5
codeseg
begin:
;for (k=1, l=0; k<4; k+=2, l++){
mov ecx, 4
shr ecx, 1
mov ebx,offset a
add ebx, 4
mov esi, offset s
fork:
;r=0;
sub edi, edi
;for (i=0; i<3; i++)
push ebx
push ecx
mov ecx, 3
;for(j=0; j<5; j++)
fori:
push ebx
push ecx
mov ecx, 3
forj:
;r+=a[i][j][k];
add edi, [ebx]
;j++
add ebx, [hj]
loop forj
;i++
pop ecx
pop ebx
add ebx, [hi]
loop fori
;s[l]=r;
mov [esi], edi
;l++
add esi, 4
pop ecx
pop ebx
add ebx, 8
loop fork
;}
Использование метода приведенных индексов для многомерных массивов
Пусть определение массива имеет вид:
Тип Имя [p1..q1, p2..q2, ...pn..qn], где pi..qi- минимальное и максимальное значения i-ого индекса
Адрес элемента массива с индексами i1, i2, ...in определяется формулой[12]:
& Имя[i1, i2, ...in] = Адресу начала массива +
- , где Hi - шаг изменения адреса для i-ого индекса.Формулы для вычисления адреса:
Hn = sizeof (Тип);
Hn-1 = Hn * (qn
- pn +1);
...
H1 = H2 * (q2
- p2 + 1).
Формулы вычисления адреса показывают, что наиболее простой вид формула имеет при начальном значении индексов, равном 0.
Пример1. Реализовать фрагмент программы:
int x[3][4][5][6];
...
x[i][j][k][l]=i+j+k+l;
x dd (3*4*5*6) dup (?)
i dd 1
j dd 2
k dd 3
l dd 4
hl equ 4
hk equ 6*hl
hj equ 5*hk
hi equ 4*hj
...
mov eax, [i]
imul eax, hi
mov ebx, [j]
imul ebx, hj
add eax, ebx
mov ebx, [k]
imul ebx, [hk]
add eax, ebx
mov ebx, [l]
mov ecx, [i]
add ecx, [j]
add ecx, [k]
add ecx, [l]
mov [x+eax+ebx*4], ecx
…
Последний метод используют для доступа к элементам массива компиляторы с языков высокого уровня, поэтому использование переменных с индексами, особенно для многомерных массивов, требует большого времени для вычисления адреса элемента массива.
Многомерные массивы
Для многомерных массивов используются:
· метод связных индексов при монотонном изменении индекса
· метод приведенных индексов, когда вычисляется адрес элемента массива в соответствующем одномерном массиве.
Задание типа структуры
Общий вид задания типа:
Struc Имя
поле1
поле2
...
ends [Имя]
Полями структуры могут быть директивы определения данных, в том числе и другие структуры, т.е. допускается вложение структур.
Пример. Определить тип для задания структуры «Возраст», содержащей день рождения в виде:
Год(2 байта) Месяц (1 байт) День (1 байт)
STRUC AGE
day db ?
month db ?
year dw ?
ends
Выделение памяти под структуру
Для выделения памяти используется запись вида:
Имя поля Тип структуры ? или значение полей структуры.
Пример:
age1 AGE ?
age2 AGE ?
Как и для стандартных типов данных, можно задавать коэффициенты повторений.
AgeList AGE 50 dup (?)
Имена полей глобальны, т.е. нельзя использовать одно и то же имя для разных структур или это имя для простых переменных.
Инициализация полей структуры
Используется 3 способа инициализации.
Способ 1. Значения полей задаются в задании типа структуры, например:
STRUC AGE
day db 1
month db 1
year dw 1980
ends
Эти значения переносятся в поля структуры, если для них не используются другие способы задания.
Способ 2. Позиционное задание полей. Значения полей задаются в порядке их следования в определении данного через запятую в угловых скобках, например
age1 AGE < 1,2, 1979>
age2 AGE < 25, 10, 1981,>
Если значение какого-то поля переопределять не нужно, оно не записывается, но запятая ставится, например:
age3 AGE <25, 10,>
В этом случае год рождения 1980, как определено в задании типа.
Если не определяется значение последних полей, запятые можно не ставить, например:
age4 AGE <25 >
Здесь месяц и год определяются из задания типа.
Если значения полей не надо переопределять, записываются просто угловые скобки, например
age5 AGE < >
Способ 3. Непозиционное задание полей. Значения полей задаются в произвольном порядке в фигурных скобках. Для каждого значения используется формат:
Имя поля = Значение.
Определения разделяются запятыми. Пример.
age5 AGE {day = 3, year = 1990, month = 2}
Особенности инициализации строк и массивов
Пусть полем структуры является строка длиной 10 символов. Чтобы можно было задавать начальные значения этому полю, необходимо его представление в виде:
pole1 db ‘ ’
В кавычках задано требуемое количество пробелов. Если строка задана в виде 10 dup ( ), компилятор ее воспринимает как данное типа 1 байт и допускает инициализацию только одного байта. При задании числовых массивов или используются разные имена для отдельных элементов, или инициализация не допустима.
Обращение к полям структуры
Для обращения к полю по имени структуры используется запись вида:
Имя структуры. Имя поля.
Если адрес структуры задан в регистре, то поле структуры задается в виде [(Имя структуры reg). Имя поля].
Пример. Задан массив с возрастами. Определить возраст самого старшего человека списка
ideal
p386
model flat
extrn ExitProcess:proc
dataseg
STRUC AGE
day db 1
month db 1
year dw 1980
ends
list AGE < 3,10>, <1,1,1993>
AGE < 1,1, 1989>, <>
count dd 4
old AGE ?
codeseg
begin:
mov eax, offset list
mov bx, [(AGE eax).year]
mov dl, [(AGE eax).month]
mov dh, [(AGE eax).day]
mov ecx, [count]
dec ecx
add eax, 4
for1:
cmp bx, [(AGE eax).year]
jl short next
jg write
cmp dl, [(AGE eax).month]
jl short next
jg short write
cmp dh, [(AGE eax).day]
jle short next
write:
mov bx, [(AGE eax).year]
mov dl, [(AGE eax).month]
mov dh, [(AGE eax).day]
next:
add eax, 4
loop for1
mov [old.year], bx
mov [old.month], dl
mov [old.day], dh
call ExitProcess
end begin
Вариант 2.
Использовать для сравнения целиком всю запись.
ideal
p386
model flat
extrn ExitProcess:proc
dataseg
STRUC AGE
day db 1
month db 1
year dw 1980
ends
list AGE < 3,10>, <1,1,1993>, < 1,1, 1989>, <>
count dd 4
old AGE ?
codeseg
begin:
mov eax, offset list
mov ebx, [ eax]
mov ecx, [count]
dec ecx
for1:
add eax, 4
cmp ebx, [ eax]
jle short next
write:
mov ebx, [ eax]
next:
loop for1
mov [dword old], ebx
call ExitProcess
end begin
Пример 2. Составить программу для упорядочивания списка студентов в порядке убывания среднего балла
Способ 1. В запись включить фамилию и оценки - недостаток - размер фамилии может содержать разное число символов - надо выделять память по максимуму.
Способ 2. Записать список фамилий. Адрес каждой фамилии -в запись. Достоинства:
памяти, сколько требуется;
при сортировке перемещается не строка, а ее адрес - уменьшается требуемое время.
Lёяюы№чєхь фы єяюЁ фюўштрэш ьхЄюф яюфёўхЄр .
TєЄ№ ьхЄюфр: -ы ърцфюую ўшёыр юяЁхфхы хь ъюышўхёЄтю ўшёхы, яЁхф°хёЄтє¦•шї фрээюьє ўшёыє. ¦рЄхь ўшёыр ёЄрт Єё эр ётюш ьхёЄр т фЁєуюь ьрёёштх.
; +яЁхфхыхэшх ъюышўхёЄтр яЁхф°хёЄтє¦•шї ўшёхы
аfor (i=0; i<n; i++) s[i]=0;
for (i=1; i<n; i++)
аа for (j=0; j<i; j++)
ааааа if (x[j]>x[i]) s[j]+=1; else s[i]+=1;
; ¦ряшё№ ўшёхы эр ётюш ьхёЄр
for (i=0; i<n;i++)
аа y[s[i]]=x[i];
ideal
p386
modelаа flat
extrn ExitProcess:proc
dataseg
fio1ааа dbааааа 'aaaaaaaa', 0
fio2ааа dbааааа 'bbbbbb', 0
fio3ааа dbааааа 'cccccccccccccccc', 0
fio4ааа dbааааа 'ddddddd', 0
fio5ааа dbааааа 'eeeeeeeeeeeeeeeeeeee', 0
struc аа data1
pfioаааааа ddаа ?
oc1ааааааа dbаа ?
oc2ааааааа dbаа ?
oc3ааааааа dbаа ?
oc4ааааааа dbаа ?
ends
gr1ааааааа data1а < offset fio1, 2, 3, 4, 5>, < offset fio2, 3,4,5,3>
аааааааааа data1а < offset fio3, 5, 5, 5, 5>, < offset fio4, 5,4,5,5>
аааааааааа data1а < offset fio5, 5, 4, 4, 4>
countааааа ddааа 5
rааааааааа data1 5 dup (?)
sааааааааа ddаа 5 dup(0)
codeseg
begin:
;for (i=1; i<n; i++)
movа ecx, [count]
decа ecx
movаааа eax, 1
movа esi, offset gr1
addааааа esi, 8
fori:
movа bl,0
addа bl, [(data1 esi).oc1]
addа bl, [(data1 esi).oc2]
addа bl, [(data1 esi).oc3]
addа bl, [(data1 esi).oc4]
;аа for (j=0; j<i; j++)
movаааа edx, 0
movаааа edi,offset gr1
forj:
cmpаааа edx, eax
jgeаааа breakj
movаааа bh, 0
addаааа bh, [(data1 edi).oc1]
addаааа bh, [(data1 edi).oc2]
addаааа bh, [(data1 edi).oc1]
addаааа bh, [(data1 edi).oc1]
;ааааа if (x[j]>x[i]) s[j]+=1; else s[i]+=1;
cmpааа bh, bl
jgeааа short mge
incааа [s+edx*4]
jmpааа short nextj
mge:
incааа [s+eax*4]
nextj:
incааа edx
addааааа edi, 8
jmpаа forj
;i++
breakj:
incа eax
addааааа esi, 8
loopаааа fori
;for (i=0; i<n;i++)
movа ecx, [count]
movа eax, 0
for2:
;аа y[s[i]]=x[i];
leaаааааа esi, [ gr1 + eax* 8]
movа ebx, [s+eax*4]
leaаааааа edi, [r+ebx*8]
movа edx, [(data1 esi).pfio]
movа [(data1 edi).pfio], edx
movа edx, [dwordа (data1 esi).oc1]
movа [dword (data1 edi).oc1], edx
;i++
incа eax
loopаа for2
call ExitProcess
end begin
Структуры
Используются для задания полей разной длины, входящих в одну запись.
Особенности инициализации полей объединения
В отличие от структур можно инициализировать только элемент первого поля. Для инициализации можно использовать такие же способы, как для структуры.
Пример. В предыдущей задаче поле оценок использовалось как 4 байта и одно 4-х байтовое слово. Задать это с помощью объединения.
STRUC STRUC1
oc1 db ?
oc2 db ?
oc3 db ?
oc4 db ?
ends
UNION UNION1
st1 STRUC1 <5,5,5,5>
st2 dd ?
ends
STRUC DATA1
pfio dd ?
oc UNION1 ?
ends
Для доступа к полям в этом случае используется запись вида:
data1.oc.st1.oc1
Объединения
Используются, если необходимо одно и тоже поле памяти использовать как данные разного типа. Отдельные поля объединения записываются в памяти, начиная с одного и того же адреса - это отличие от структуры.
Общий вид объединения
UNION Имя
поле1
поле2
...
ends [Имя]
Требования и способы задания полей как для структур.
Перечисления
Общий вид:
Имя типа ENUM {Имя поля [= значение], Имя поля [= значение],... }
Перечисление используется для задания группы констант, чтобы не повторять директивы EQU или =. Имя поля является именем константы. Значение первого поля, если оно не задано, принимается равным 0, значение любого другого поля, если оно не задано, равно значению предыдущего, увеличенному на 1. Значения разных полей в списке могут повторяться.
Пример.
Запись COLORS ENUM {BLUE, GREEN, RED} эквивалентна определению трех констант:
BLUE EQU 0
GREEN EQU 1
RED EQU 2
Поля объединений можно использовать в любом месте, где допускается использование константы.
Основные команды для работы с битами
Основные команды заданы в табл. 10.1
Таблица 10.1. Основные команды для работы с битами
Но-мер | Назначение | Код | Выполняемые действия | Формируемые флаги | |||||
1 | Побитовое сложение | OR | оп1|=оп2 | c=0, o=0, z, p, s | |||||
2 | Побитовое умножение | AND | оп1&=оп2 | c=0, o=0, z, p, s | |||||
3 | Проверка | TEST | оп1&оп2 | c=0, o=0, z, p, s | |||||
4 | Побитовое
Отрицание | NOT | ~оп1 | Флаги не изменяются | |||||
5 | Сложение по модулю 2 | XOR | оп1^=оп2 | c=0, o=0, z, p, s |
Примеры использования команд.
Пример1. Записать команды для:
· установки в 1 заданного бита в байте;
· установки в 0 заданного бита в слове;
· инвертирования заданного бита в двойном слове;
· проверки заданного бита в двойном слове;
Пусть номер бита является константой
BitNumber EQU 3
b db 37h
w dw 1234h
d1 dd 12345678h
d2 dd 12345678h
...
; установка в 1 заданного бита в байте
OR b, 1 SHL BitNumber
; установка в 0 заданного бита в слове
AND w, NOT (1 SHL BitNumber)
; инвертирование заданного бита в двойном слове
XOR d1, 1 SHL BitNumber
проверка заданного бита в двойном слове
TEST d2, 1 SHL BitNumber
jz zero
...
zero:
Пример 2.
Задан массив байтов. Переписать в другой массив те байты первого массива, в которых биты 0 и 4 единичные, биты 1, 3, 7 - нулевые, значения остальных битов не имеет значения.
Для решения этой задачи сформируем маски для выделения требуемых битов и проверки заданных битов на 1. Первая маска может быть сформирована так:
(1 shl 0) or (1 shl 4) or (1 shl 1) or (1 shl 3) or (1 shl 7).
Вторая маска имеет вид:
(1 shl 0) or (1 shl 4).
Ideal
p386
model flat
extrn ExitProcess:proc
dataseg
a db 11h, 12h, 23h, 34h, 56h, 78h, 90h, 0a1h, 0b2h, 0c3h, 0d4h, 0e5h
count dd 11
b db 11 dup (0)
codeseg
begin:
mov ecx, [count]
mov eax, 0; индекс исходного массива
mov edx, 0 ;индекс результирующего массива
fori:
mov bl, [a+eax]
mov bh, bl
and bl, (1 shl 0) or (1 shl 4) or (1 shl 1) or (1 shl 3) or (1 shl 7)
xor bl, (1 shl 0) or (1 shl 4)
jnz short next
mov [b+edx], bl
inc edx
next:
inc eax
loop fori
call ExitProcess
end begin
В этом примере только первое число удовлетворяет поставленным требованиям.
Пример 3. Вычислить значение булевского выражения.
Значение выражения равно истине, если значение хотя бы одного слагаемого равно истине. Для выделения требуемых битов и проверки единичных битов используются константы:
; Первое слагаемое
c11 db 11010110b
c12 db 10000110b
; Второе слагаемое
c21 db 01101100b
c22 db 01001000b
; Третье слагаемое
c31 db 00000111b
c32 db 00000101b
Пусть вычисляется значения для байта
x db 10101010b
Результат записывается в поле RES и равен 0 для ответа «Ложь» и 1 для ответа «Истина».
RES db ?
; Проверка первого слагаемого
mov RES, 1; Пусть ответ равен «Истина»
mov al, [x]
and al, [c11]
xor al, [c12]
jz short true
; Проверка второго слагаемого
mov al, [x]
and al, [c21]
xor al, [c22]
jz short true
; Проверка третьего слагаемого
mov al, [x]
and al, [c31]
xor al, [c32]
jz short true
mov [RES], 0
true:
Классификация команд
Сдвиг может быть обычным и циклическим. При обычном сдвиге разряды, выходящие за разрядную сетку, уничтожаются. Для циклического сдвига они записываются на место освободившихся разрядов.
Обычный сдвиг может быть арифметическим и логическим. При арифметическом сдвиге вправо свободные позиции слева заполняются знаковым (старшим) разрядом. При логическом сдвиге вправо свободные разряды слева заполняются нулями. Арифметический сдвиг вправо используется обычно для деления нацело знакового числа на 2n, а логический - для беззнакового числа. Операции сдвига влево для обоих типов команд выполняются одинаково.
Циклический сдвиг может быть с переносом и без переноса. Схема выполнения команд для обоих типов сдвига на 1 бит вправо представлен на рис. 10.1a и рис. 10.1b.
а - циклический сдвиг с переносом
b- циклический сдвиг без переноса
Рис. 10.1 Схема выполнения циклического сдвига
Стрелка вверху задает направление сдвига (вправо). Как видно из рисунка, выдвигаемый бит всегда помещается в бит переноса С. При циклическом сдвиге предыдущее содержимое бита переноса записывается в освободившийся разряд, при обычном – теряется, а в освободившийся разряд записывается выдвигаемый бит.
Заметим, что при любом сдвиге последний из выдвинутых разрядов записывается вместо бита переноса.
Кодировка команд сдвига
Первая буква кода определяет тип сдвига. Буква S соответствует обычному, а буква R циклическому сдвигу. Вторая буква уточняет тип сдвига. Для обычного сдвига используется буква A для арифметического и буква H для логического сдвига. Для циклического сдвига вторая буква С
соответствует сдвигу с переносом и буква O сдвигу без переноса. Третья буква определяет направление сдвига. Буква R
соответствует сдвигу вправо, и буква L сдвигу влево. Таким образом коды команд сдвига:
Общий вид команд сдвига
Общий вид команд сдвига:
код оп1, оп2
оп1 - сдвигаемое данное. Может быть байтом. Словом или двойным словом. Может быть задано в общем регистре или памяти.
оп2 - задает, на сколько разрядов выполняется сдвиг. Может быть константой или регистром CL, в который предварительно необходимо записать константу сдвига.
Примеры использования команд сдвига.
Пример 1. Установить в 1 бит i
двойного слова x, если значение i вычисляется в программе.
Mov cl, [i]
mov 1, eax
shl eax, cl
or [x], eax
Пример 2. Задан массив чисел, каждое число не более 32. Упаковать этот массив таким образом, чтобы под каждое число отводилось 5 бит.
Ideal
p386
model flat
extrn ExitProcess:proc
dataseg
x db 3,5,7,9,2,4,6
xpack db (((xpack-x)*8+4)/5) dup (0)
count dd xpack - x
codeseg
begin:
mov eax, [count]
; for (i=0; i< count; i++){
unsigned *p = xpack+(i*5)/8
}
mov esi, 0;
mov ebx, 0
fori:
mov dx, 0
mov ecx, ebx
and ecx, 7
mov dl, [x+esi]
shl dx, cl
mov ecx, ebx
shr ecx, 3
or [xpack+ecx], dx
add ebx, 5
inc esi
cmp esi, [count]
jl fori
call ExitProcess
end begin
Группа команд: проверить и изменить
Команды проверяют значение заданного бита (записывают его содержимое в бит C) и меняют значение в соответствии с заданным кодом. Первый операнд задает проверяемое и изменяемое данное, второй - номер бита. В качестве первого операнда может быть регистр и память размером 1, 2 или 4 байта. Второй операнд задается константой или регистром. Команды для проверки и изменеия заданных битов представлены в табл. 10.2
Таблица 10.2. Команды для проверки и изменеия заданных битов
Назначение | Код | Выполняемые действия | |||
Проверка бита | Bt | C=(oп1 & (1<<оп2))>> оп2 | |||
Проверка и установка бита | Bts | C=(oп1 & (1<<оп2))>> оп2;
oп1 |= (1<<оп2)) | |||
Проверка и сброс бита | Btr | C=(oп1 & (1<<оп2))>> оп2;
oп1 &= (~(1<<оп2)) | |||
Проверка и инвертирование бита | Btc | C=(oп1 & (1<<оп2))>> оп2;
oп1 ^= (1<<оп2)) |
Примеры использования команд
Пример 1. Вычислить сумму всех битов, стоящих на четных местах в двойном слове
p486
ideal
model flat
extrn ExitProcess:proc
dataseg
x dd 0a5a5a5a5h
s dd ?
codeseg
begin:
xor edx, edx; s=0
mov eax, [x]
mov ecx, 16
mov ebx, 0
for:
bt eax, ebx
adc edx, 0
add ebx, 2
loop for
mov [s], edx
call ExitProcess
end begin
Программа выдает ответ 8.
Команды длинного сдвига
Общий вид команды:
,где оп1 - задает, что сдвигается (память или 32 битный регистр);
оп2 - откуда вдвигаются разряды на освободившиеся места (32 - битный регистр);
оп3 - на сколько сдвиг (константа или регистр CL ).
Схема выполнения команды SHLD представлена на рис. 10.2
Рис. 10.2
Заметим, что содержимое оп2 сдвигается виртуально, фактическое содержимое этого регистра не изменяется.
Пример использования команды.
Задано число длиной 1024 бита. Разделить его на 8.
Для деления числа на 8 достаточно сдвинуть его на 3 бита вправо, в этом случае получим частное т деления. Остаток равен младшим трем битам исходного числа.
p486
ideal
model flat
y=x>>3; ost = x[0]&7
extrn ExitProcess:proc
dataseg
x dd 32 dup (0ffffffffh)
y dd 32 dup (?); Частное
ost dd ? ; Остаток
codeseg
begin:
; y=x
mov ecx, 32
for1:
mov eax, [x+ecx*4-4]
mov [y+ecx*4-4], eax
loop for1
;for (i=0; i<31; i++)
mov ecx, 31
mov eax, 0
; shrd (y[i], y[i+1], 3);
for2:
mov ebx, [y+eax*4+4]
shrd [y+eax*4], ebx, 3
;i++
inc eax
loop for2
;shr (y[i], 3);
shr [y+eax*4], 3
; ost = x[0] & 7
mov eax, [x]
and eax, 7
mov [ost], eax
call ExitProcess
end begin
Дополнительные команды
Следующие две команды позволяют осуществить поиск первого установленного в 1 бита операнда. Поиск можно произвести как с начала так и от конца операнда:
bsf
операнд_1,операнд_2 (Bit Scaning Forward) - сканирование битов вперед.
Команда просматривает (сканирует) биты операнд_2
от младшего к старшему (от бита 0 до старшего бита) в поисках первого бита, установленного в 1. Если таковой обнаруживается, в операнд_1 заносится номер этого бита в виде целочисленного значения. Если все биты операнд_2 равны 0, то флаг нуля zf устанавливается в 1, в противном случае флаг zf сбрасывается в 0.
Пример:
mov al,02h
bsf bx,al ;bx=1 jz m1 ;переход, если al=00h ...
|
bsr операнд_1,операнд_2
(Bit Scaning Reset) — сканирование битов в обратном порядке. Команда просматривает (сканирует) биты операнд_2
от старшего к младшему (от старшего бита к биту 0) в поисках первого бита, установленного в 1. Если таковой обнаруживается, в операнд_1 заносится номер этого бита в виде целочисленного значения.
При этом важно, что позиция первого единичного бита слева отсчитывается все равно относительно бита 0. Если все биты операнд_2
равны 0, то флаг нуля zf
устанавливается в 1, в противном случае флаг zf
сбрасывается в 0. Заметим, что эти команды для tasm32 не компилируются
Определение типа битовой структуры
Общий вид типа:
record Имя поле1, поле2, ...
Для задания поля используется запись:
Имя: Ширина[= Значение]
Поля задаются, начиная с поля, соответствующего старшим разрядам. Суммарная ширина не может превосходить 32 бит. Если суммарная ширина не равна полному числу байтов, то число располагается в младших разрядах. Старшие разряды обнуляются.
Выделение памяти под битовую структуру
Общий вид директивы выделения памяти:
Тип структуры Имя данного <Значения полей>
{ Значения полей }
Значения полей задаются как для обычной структуры в угловых скобках (позиционный метод) или в фигурных (вместе с именем поля). Если значение поля не задано, оно обнуляется.
Особенности использования битовых структур
Правила:
· Если в команде используется имя данного, само
значение данного используется.
· Если в команде задано имя поля, то результатом является константа сдвига, которая показывает положение поля в записи.
· Если в команде используется операция mask имя поля или mask имя типа, то формируется маска для выделения поля (записи).
· Если в команде используется операция width имя поля или width имя типа, то формируется ширина поля (записи) в битах.
Пример использования битовой структуры
Дата создания файла задается в виде:
день : 5 битов;
месяц : 4 бита;
год: 7 битов.
Год задает разность между текущим годом и 1970[13]
годом.
Значения для текущей даты упаковать в структуру и
проверить правильность упаковки.
ideal
p486
model flat
extrn ExitProcess:proc
dataseg
record data1 y:7, m:4, d:5
d1 data1 <>
day db 1
month db 11
year dw 2000
codeseg
begin:
movzx ax, [day]
movzx bx, [month]
mov cl, m
shl bx, cl
or ax, bx
mov bx, [year]
sub bx, 1970
mov cl, y
shl bx, cl
or ax, bx
mov [d1], ax
mov ax, [d1]
mov bx, mask d
and bx, ax
mov [day], bl
mov bx, mask m
and bx, ax
mov cl, m
shr bx, cl
mov [month], bl
mov bx, mask y
and bx, ax
mov cl, y
shr bx, cl
add bx, 1970
mov [year], bx
call ExitProcess
end begin
Использование битовых структур позволяет сделать программу более мобильной. При изменении размера полей достаточно изменить только определение структуры, все остальные команды изменять не надо.
Типы программ
Одномодульная и многомодульная.
Одномодульная программа - в одном файле. Многомодульная состоит из нескольких файлов.
Использование функций
Функции позволяют оформлять независимые участки
программы в виде отдельных модулей, что значительно упрощает отладку программы и уменьшает ее размер. Ниже будут рассмотрены команды для работы с функциями, структура функций и способы передачи параметров. Вначале даются общие положения для функций. Команды для работы с процедурами(функциями)
Основные команды для работы с функциями приведены в табл. 11.1, а директивы в табл. 11.2
Таблица 11.1 Команды для работы с функциями
Назначение | Общий вид команды | Выполняемые действия | |||
Вызов функции | call операнд[14] | push EIP
MOV EIP, операнд | |||
Возврат в вызывающую программу | ret [Число] | Esp+=Число
Pop EIP |
Таблица 11.2. Директивы для работы с функциями
Назначение | Общий вид директивы | ||
Заголовок функции | имя proc или proc имя | ||
Директива конца | [Имя] endp или endp [Имя] |
Общий вид функции для режима masm:
имя proc
[; Сохранение используемых регистров]
.................
[; Восстановление используемых регистров]
ret
имя endp
Общий вид функции для режима ideal:
proc имя
[; Сохранение используемых регистров]
.................
[; Восстановление используемых регистров]
ret
endp имя
Функция одномодульной программы должна быть частью сегмента кодов. Может быть задано несколько функций последовательно.При выполнении программ переход к функции необходимо выполнять только по команде call, а не в результате естественного выполнения команд, поэтому функции помещаются или в начало сегмента кода до точки входа, или в конце после команд для выхода из программы.
Передача параметров через регистры
Для задания параметров или их адресов используются регистры.
Пример 1. Составить вызывающую программу и функцию для вычисления наибольшего общего делителя для двух целых положительных чисел с помощью алгоритма Эвклида.
Для данной программы исходными данными являются 2 целых числа, результатом является значение наибольшего общего делителя. Параметрами являются значения 2-х чисел и адрес результата. Пусть исходные данные передаются через регисты EAX, EBX, а адрес результата через регистр ECX
IDEAL
p586
MODEL FLAT
extrn ExitProcess:proc
DATASEG
X DD 150
Y DD 120
Z DD ?
CODESEG
begin:
mov eax, [X]
mov ebx, [Y]
mov ecx, offset Z
call gcd
call ExitProcess
; Процедура
proc gcd
push edx
; if (x<y) swap(x, y);
cmp eax, ebx
jge short m1
xchg eax, ebx
; if (y==0) return x
m1:
test ebx, ebx
je short break
; while(1)
for:
xor edx, edx
div ebx
test edx, edx
je short break
mov eax, ebx
mov ebx, edx
jmp for
break:
mov [ecx], ebx
pop edx
ret
endp gcd
end begin
Достоинство способа - наиболее быстрый способ. Недостаток - недостаточное число регистров.
Пример 2. Составить процедуру для вычисления длины строки с нулевым завершителем. Пусть адрес начала строки в регистре EBX, а
адрес длины строки возвращается через регистр EAX.
; Процедура для вычисления длины строки с нулевым
; завершителем. Пусть адрес начала строки в регистре
; EBX, а адрес длины строки - через регистр EAX.
ideal
P486
MODEL flat
extrn ExitProcess:proc
DATASEG
str1 db 'Это первая строка', 0
str2 db 'А это вторая', 0
n dd ?, ?
CODESEG
begin:
lea ebx, [str1]
lea eax, [n]
call strlen
lea ebx, [str2]
lea eax, [n+4]
call strlen
call ExitProcess
proc strlen
push ecx edx
xor ecx, ecx
for:
mov dl, [ebx] ; while (str[i])ax++;
and dl, dl
je short lend; конец строки
inc ecx
inc ebx
jmp for
lend:
mov [eax], ecx
pop edx ecx
ret
endp strlen
end begin
Недостатки использования регистров для передачи параметров:
1. Можно использовать только для передачи данных длиной 1, 2, 4 байта или адресов:
2. Количество регистров ограничено.
Для преодоления недостатков можно все параметры записать в структуру и передать адрес начала структуры в единственном регистре или использовать для передачи параметров стек.
Передача параметров через общую область памяти
Т.к. в одномодульной программе вызывающая программа и функция расположены в одном файле, функция может непосредственно использовать переменные программы.
Пример.
Составить функцию для обмена местами значения двух переменных.
ideal
P486
MODEL flat
extrn ExitProcess:proc
DATASEG
x dd 5
y dd 3
CODESEG
begin:
call swap
call ExitProcess
proc swap
push eax ebx
mov eax, [x]
mov ebx, [y]
mov [y], eax
mov [x], ebx
pop ebx eax
ret
endp swap
end begin
Недостатки использования переменных из сегмента данных:
1. Функция должна использоваться только для переменных с заданными именами, использовать ее для других переменных нельзя.
2. Этот способ не применим для многомодульных программ
Общей областью можно считать стек, в который помещаются параметры. Рассмотрим особенности передачи параметров через стек.
При передаче параметров через стек возникает проблема доступа к ним. Из всех режимов адресации выберем режим, связанный с адресацией в стеке. В этом режиме в качестве базисного регистра используется регистр EBP, именно этот регистр будем применять для задания элемента стека. С другой стороны, указатель стека, т.е. адрес последнего занесенного элемента в стек расположен в регистре ESP. Чтобы иметь доступ к данным стека используются команды:
push ebp ; сохранение EBP
mov ebp, esp ; EBP=ESP
Эти команды должны присутствовать в любой функции, в кото-
рой параметры передаются через стек.
Передача в списке параметров простых переменных
Параметры можно разделить на параметры - исходные данные и параметры-результаты. Для первых передаются значения, а для вторых - адреса. Чтобы уменьшить число передаваемых параметров, для массивов передаются адреса, а не значения элементов, даже для массивов исходных данных. Чтобы стек не переполнялся, после использования параметры должны быть извлечены из стека. Эту функцию, называемую очисткой стека, обычно выполняет функция . Для этого в команде ret задается количество байтов, используемых для параметров, т.е. используется формат команды ret <число>. В этом случае сначала очищается стек, а затем выполняется возврат в вызывающую программу.
Ниже рассмотрены примеры использования функций с передачей
параметров через стек. Примеры отражают приемы передачи простых переменных (исходных данных и результатов), массивов, структур, и адресов функций.
Пример 1. Составить функцию вычисления z=x+y и использовать ее для вычисления c=a+b; f=d+e.
;Функция для вычисления z=x+y и использовать ее для вычисления c=a+b; f=d+e
IDEAL
P586
MODEL FLAT
EXTRN ExitProcess:proc
DATASEG
a DD 5
b DD 3
c DD ?
d DD 4
e DD -5
f DD ?
CODESEG
begin:
push [a] ; запись в стек a, b
push [b]
lea eax, c ;Формирование & c
push eax ; и запись его в стек
call summa ; Вызов функции
push [d] ; запись в стек d, e
push [e]
lea eax, [f] ; Формирование & f
push eax
call summa ; Вызов функции
call ExitProcess
proc summa | 0 | EBP | |||||||
push ebp | 4 | EIP | |||||||
mov ebp, esp | 8 | &c | |||||||
Push eax ebx | 12 | B | |||||||
mov eax, [ebp+16] ; a | 16 | A | |||||||
Add eax, [ebp+12] ; a+b | |||||||||
mov ebx, [ebp+8] ; &c | |||||||||
mov [ebx], eax ; c=a+b | |||||||||
Pop ebx eax ebp | |||||||||
Ret 12 | |||||||||
Endp summa | |||||||||
End begin |
В примере показана передача простых переменных (исходных данных и результатов). Показано состояние стека после обращения к функции для вычисления c = a + b. Анализ программы показывает, что передача параметров с последующей очисткой стека, передача управления процедуре требует дополнительных команд. Эти же действия выполняются при программировании на языке высокого уровня. Поэтому, прежде чем писать функцию на любом языке, подумайте, эффективна ли она! Решение предыдущего примера без функции явно предпочтительней!
Передача в списке параметров одномерных массивов
Для одномерных массивов – и исходных данных и результатов – передаются адреса. Это исключает необходимость копирования исходных массивов.
Пример 1. Составить функцию для объединения двух строк и использовать ее для объединения строк, содержащих слова "first " и "second " в одну строку.
IDEAL
P586
MODEL FLAT
Extrn ExitProcess:proc
DATASEG
str1 db 'first ', 0
str2 db 'second', 0
rez db (rez-str1-1) dup (?)
CODESEG
begin:
push offset str1 offset str2 offset rez ; передача адресов массивов
call strcat ;вызов процедуры
call ExitProcess
proc strcat; | 0 | EBP | |||
push ebp; | 4 | EIP | |||
mov ebp, esp; | 8 | REZ | |||
Push eax ebx ecx; | 12 | STR2 | |||
; for (I=0; str1[I]; I++) rez[I]=str1[i]; | 16 | STR1 | |||
Mov EAX, [EBP+16]; | |||||
Mov EBX, [EBP+8]; | |||||
For1: | |||||
Mov CL, [EAX] | |||||
Test Cl, cl | |||||
Je break1 | |||||
Mov [EBX], cl
Inc eax Inc ebx | |||||
Jmp For1
Break1: Mov EAX, [ebp+12] For2: | |||||
Mov CL, [EAX] | |||||
Mov [EBX], cl
Test Cl, cl | |||||
Je break2 | |||||
Inc eax
Inc ebx Jmp For2 Break2: |
Pop ecx ebx eax ebp
Ret 12
Endp
End begin
В этом примере признаком конца исходных данных является нулевой завершитель (символ с кодом 0). В результирующую строку завершитель записывается после копирования второй строки. Для массивов исходных данных (str1, str2) и массива результата (rez) передаются адреса. Это делается для экономии стекового простран-
ства и числа команд, необходимых для передачи параметров.
Пример 2. Составить программу для записи в третий массив тех чисел первого, которых нет во втором.
ideal
p386
model flat
extrn ExitProcess:proc
dataseg
x dd 1,2,3,4,5,6
ALаааааааааааааааааа - Ёхчєы№ЄрЄ - срщЄ
AXаааааааааааааааааа - Ёхчєы№ЄрЄ - ёыютю
EAXаааааааааааааааа - Ёхчєы№ЄрЄ Ц фтющэюх ёыютю
EDX, EAXаааааа - Ёхчєы№ЄрЄ Ц 2 фтющэvї ёыютр
аааа ¦ЁшьхЁ 3.а TюёЄртшЄ№ ЇєэъЎш¦ фы ёЁртэхэш "фышээvї" ўшёхы. LєэъЎш фюыцэр тючтЁр•рЄ№ 0, хёыш ўшёыр ёютярфр¦Є, 1 Ц хёыш яхЁтюх ўшёыю сюы№°х тЄюЁюую ш Ц1 т яЁюЄштэюь ёыєўрх.
ideal
p686
modelаа flat
extrn ExitProcess:proc
dataseg
xааааааааа ddааааааа 1000 dup (0ffffffffh)
nааааааааа ddааааааа (n-x)/4
yааааааааа ddааааааа 1000 dup (0ffffffffh)
resаааааа ddааааааа ?
t1аааааааа ddааааааа ?
t2аааааааа ddааааааа ?
codeseg
begin:
pushаааа [n] offset x offset yаа
rdtsc
movаааа ecx,eax
callааааа m_cmp
movаааа [res], eax
rdtsc
subаааааа eax, ecx
movаааа [t1], eax
pushаааа [n] offset x offset yаа
rdtsc
movаааа ecx,eax
callааааа m_cmp
movаааа [res], eax
rdtsc
subаааааа eax, ecx
movаааа [t1], eax
pushаааа [n] offset x offset yаа
rdtsc
movаааа ecx,eax
callааааа m_cmp1
movаааа [res], eax
rdtsc
subаааа eax,ecx
movаааа [t2], eax
call ExitProcess
;for (i=n-1; i>=0; i--){ааааааааааааааааааааааа ebpааааа 0
;ааа if (x[i]>y[i]) return -1;а ааа eipаааааа 4
;ааа if (x[i]<y[i]) return 1;аа аааа yааааааааа 8
;}ааааааааааааааааааааааааааааа аааааааааааааа xааааааааа 12
;return 0;ааааааааааааааааааааа аааааааааааааааааааааа nааааааааа 16
proc ааа m_cmp
push ebp;
movа ebp,а esp;
Pushа ebx ecx edx
movаааа ecx, [ebp+16]; n
movаааа eax, [ebp+12]; &x
movаааа ebx, [ebp+8] ; &y
fori:
movаааа edx, [eax+ecx*4-4]
cmpаааа edx, [ebx+ecx*4-4]
jbаааааааа short m1; x[i]< y[i]
jaаааааааа short m2; x[i]> y[i]
loopаааа fori
subаааааа eax, eax
m1:
movаааа eax, -1
jmpааааа short m3
m2:
movаааа eax, 1
m3:
popааааа edx ecx ebx ebp
Retаааааа 12
Endp
proc ааа m_cmp1
push ebp;
movа ebp,а esp;
Pushаа ecx esi edi
movаааа ecx, [ebp+16]; n
movаааа esi, [ebp+12]; &x
leaаааааа esi, [esi + ecx*4-4]
movаааа edi, [ebp+8] ; &y
leaаааааа edi, [edi + ecx*4-4]
std
repeаааа cmpsd
jbаааааааа short m11; x[i]< y[i]
jaаааааааа short m12; x[i]> y[i]
subаааааа eax, eax
m11:
movаааа eax, -1
jmpааааа short m13
m12:
movаааа eax, 1
m13:
cld
popааааа edi esi ecx ebp
Retаааааа 12
Endp
end begin
¦рьхЄшь, ўЄю лЎшЇЁv¬ ўшёыр ёЁртэштр¦Єё , эрўшэр ёю ёЄрЁ°хщ ЎшЇЁv. ¦рцфр ЎшЇЁр ёЁртэштрхЄё ъръ схччэръютюх ўшёыю. TЁртэхэшх яЁюфюыцрхЄё фю Єхї яюЁ, яюър эрщфхь Ёрчэvх ЎшЇЁv шыш эх ёЁртэшь тёх ЎшЇЁv ўшёыр.
+сЁрЄшЄх тэшьрэшх эр эютvх ъюьрэфv фы юяЁхфхыхэш ъюышўхёЄтр ЄръЄют фы тvяюыэхэш ЇєэъЎшщ. ¦юьрэфр RDTSC (Read Time-stamp counter) - тvяюыэ хЄё фы яЁюЎхёёюЁют, эрўшэр ё PENTIUM MMX ш тючтЁр•рхЄ т ЁхушёЄЁх EAX
+Єюсv юяЁхфхышЄ№, яюффхЁцштрхЄё ыш фрээр ъюьрэфр фы яЁюЎхёёюЁр, шёяюы№чєхЄё ъюьрэфр CPUIDЧCPU Identification
¦Єр ъюьрэфр Єръцх яючтюы хЄ юяЁхфхышЄ№ яЁюшчтюфшЄхы яЁюЎхёёюЁр, ёхьхщёЄтю, ъюЄюЁюьє юэ яЁшэрфыхцшЄ, ьюфхы ш тхЁёшш, р Єръцх фЁєує¦ шэЇюЁьрЎш¦. -ы юяЁхфхыхэш Єшяр тvфртрхьющ шэЇюЁьрЎшш шёяюы№чєхЄё ЁхушёЄЁ EAX ( Єрсы 11.3). ¦юьрэфр CPUID ьюцхЄ тvяюыэ Є№ё фы ы¦сюую єЁютэ яЁштшыхушщ.
TрсышЎр 11.3
Tїюфэvх фрээvх |
Tvїюфэvх фрээvх |
EAX = 0 |
EAX Ц ьръёшьры№эюх чэрўхэшх, ъюЄюЁюх ьюцэю чрфртрЄ№ эр тїюфх (юсvўэю 2) EBX, EDX, ECX Ц яЁюшчтюфшЄхы№ Ц -ы PENTIUM ёЄЁюър лGenuineIntel¬ ЁрёяЁхфхыхэр Єръ: EBXа ?756e6547h (* "Genu", сєътр G т BL *) EDXа ?49656e69h (* "ineI", сєътр i т -DL *) ECXа ?6c65746eh (* "ntel", сєътра n т CL *) |
EAX = 1 |
EAX Ц тхЁёш яЁюЎхёёюЁр (Єшя, ёхьхщёЄтю, ьюфхы№, step) EBX - ЁхчхЁт ECX- ЁхчхЁт EDX Ц шэЇюЁьрЎш юс юёюсхээюёЄ ї яЁюЎхёёюЁр |
EAX = 2 |
EAX ЦшэЇюЁьрЎш ю ъ¤°х EBX -ЦшэЇюЁьрЎш ю ъ¤°х ECX- ЦшэЇюЁьрЎш ю ъ¤°х EDX Ц ЦшэЇюЁьрЎш ю ъ¤°х |
¦рёёьюЄЁшь ёюфхЁцшьюх Єюы№ъю эхъюЄюЁvї сшЄют ЁхушёЄЁр EDX:
23 Ц яюффхЁцштрхЄё MMX Ц Єхїэюыюуш ;
18 Ц яЁюЎхёёюЁ яюффхЁцштрхЄ 96 сшЄэvщ єэшъры№эvщ эюьхЁ яЁюЎхёёюЁр.
4 Ц яюффхЁцштрхЄё ъюьрэфр RDTSC
¦ЁшьхЁ 4. TюёЄртшЄ№ ЇєэъЎш¦, ъюЄюЁр тючтЁр•рхЄ юЄтхЄ 1, хёыш ъюьрэфр RDTSC яюффхЁцштрхЄё ш 0 т яЁюЄштэюь ёыєўрх.
ideal
p686
modelаа flat
extrn ExitProcess:proc
dataseg
resаааааа ddааааааа ?
codeseg
begin:
callааааа IsReadCount
movаааа [res], eax
call ExitProcess
proc ааа IsReadCount
movаааа eax, 1
cpuid
movаааа eax, 1
testаааааа edx, 1 shl 4
jnzааааааа short m1
subаааааа eax, eax
m1:
Retаааааа
Endp
end begin
Передача в списке параметров многомерных массивов
При рассмотрении механизма определения адреса для двухмерного массива было замечено, что для определения адреса текущего элемента массива необходимо знать размер одного элемента
и длину строки (или количество элементов в одной строке). Поэтому в списке параметров необходимо обязательно задавать непосредственно длину строки или параметр, который позволит вычислить эту длину.
Пример 1 . Составить главную программу и функцию для умножения заданного столбца матрицы на заданное значение.
ideal
p686
model flat
extrn ExitProcess:proc
dataseg
matr dd 1, 1, 1, 1
dd 2, 2, 2, 2
dd 3, 3, 3, 3
value dd 4
col dd 1
n dd 4; количество столбцов
m dd 3; количество строк
codeseg
begin:
push offset matr [n] [m] [value] [col]; ebp 0
call mc_mul; eip 4
call ExitProcess; col 8
proc mc_mul; value 12
push ebp; m 16
mov ebp, esp; n 20
; matr 24
;for(i=0; i<m; i++)
; matr[i][col]*=value;
push eax ebx ecx esi
mov ecx, [ebp+16]
mov esi, [ebp+24]
mov ebx, [ebp+8]; col
lea esi, [esi+ebx*4]; & начала
mov ebx, [ebp+20]
shl ebx, 2; Размер строки
mov edi, [ebp+12]
for1:
mov eax, [esi]
mul edi
mov [esi], eax
add esi, ebx
loop for1
pop esi ecx ebx eax ebp
Ret 5*4
Endp
end begin
Пример 2.
Для заданного массива строк определить длины строк
ideal
p686
model flat
extrn ExitProcess:proc
dataseg
s1 db 'a', 0
s2 db 'bb', 0
s3 db 'ccc', 0
s4 db 'dddd', 0
array dd s1, s2, s3, s4
n dd (n-array)/4
lenаааааа ddааааааа 4 dup (?)
codeseg
begin:
push ааа offset array [n] offset lenаааааааа
callааааа TextLen
call ExitProcess;
proc ааа TextLen;аа
pushаааа ebp
movаааа ebp, esp;ааааааааааааааааааааааааааааааааа TT+¦
;for (i=0; i<n; i++ ){ааааааааааааааааааааааааа ebpааааа 0
;аа for (j=0; array[i][j]; j++);аааа ааааааа eipаааааа 4
;аа len[i]=j;аааааааааааааааааааааааааа аааааааааааааа lenаааааа 8
;}ааааааааааааааааааааааааааааааааааааа аааааааааааааааааа nааааааааа 12
;аааааааааааааааааааааааааааааааааааааа ааааааааааааааааааа arrayаа 16
pushad
movаааа ecx, [ebp+12]
movаааа eax, [ebp+16]
movаааа edi, [ebp+8]
fori:
subаааааа edx, edx
leaаааааа ebx, [eax+ecx*4-4]
movаааа esi, [ebx]
forj:
movаааа bl, [esi+edx]
testаааааа bl, bl
jeаааааааа short breakj
incаааааа edx
jmp аааа forj
breakj:
movаааа [edi+ecx*4-4], edx
loopаааа fori
popad
popааааа ebp
Retаааааа 3*4ааааа
Endp
end begin
+сЁрЄшЄх тэшьрэшх эр ЁрсюЄє ё рфЁхёюь ьрёёштр рфЁхёют!
¦ЁшьхЁ 3. LяюЁ фюўшЄ№ ьрёёшт ёЄЁюъ т яюЁ фъх єсvтрэш фышэ ёЄЁюъ.
ideal
p686
modelаа flat
extrn ExitProcess:proc
dataseg
s1аааааааа dbааааааа 'a', 0
s2аааааааа dbааааааа 'bb', 0
s3аааааааа dbааааааа 'ccc', 0
s4аааааааа dbааааааа 'dddd', 0
arrayаа ddааааааа s1, s2, s3, s4
n аааааааа ddааааааа (n-array)/4
lenаааааа ddааааааа 4 dup (?)
codeseg
begin:
push ааа offset array [n] offset lenаааааааа
callааааа TextLen
push ааа offset array [n] offset len
callааааа SortLenааааааааааа
call ExitProcess;
proc ааа TextLen;аа
pushаааа ebp;ааааааааааааааааааааааааааааааааааа
movаааа ebp, esp;ааааааааааааааааааааааааааа ааа
;for (i=0; i<n; i++ ){ааааааааааааааааааааааааа ebpааааа 0
;аа for (j=0; array[i][j]; j++);ааааааа eipааааааааааа 4
;аа len[i]=j;аааааааааааааааааааааааааа lenааааааааа 8
;}ааааааааааааааааааааааааааааааааааааа nаааа 12
;аааааааааааааааааааааааааааааааааааааа arrayаааааааааа 16
pushad
movаааа ecx, [ebp+12]
movаааа eax, [ebp+16]
movаааа edi, [ebp+8]
fori:
subаааааа edx, edx
leaаааааа ebx, [eax+ecx*4-4]
movаааа esi, [ebx]
forj:
movаааа bl, [esi+edx]
testаааааа bl, bl
jeаааааааа short breakj
incаааааа edx
jmp аааа forj
breakj:
movаааа [edi+ecx*4-4], edx
loopаааа fori
popad
popааааа ebp
Retаааааа 3*4ааааа
Endp
proc SortLen
pushаааа ebp
movаааа ebp, esp
pushad
;for (i=1; i<n; i++){аааааааааааааааааааааааааааааааааааааа ebpааааа 0
;аа r=x[i];аааааааааааааааааааааааааааааааааааа аааааааааааааааааааааааааааааа eipаааааа 4
;аа for (j=i-1; j>=0; j--)аааааааааааааааааааааааааааааааааааааааааааааа arrayаа 8ааааааааа
;аааааааааа if (x[j]<r)x[j+1]=x[j]; else break;аааа ааааааааааа nааааааааа 12аааа
;аа x[j+1]=r;аааааааааааааааааааааааааааааааааа аааааааааааааааааааааааааааа lenаааа 16
;}
movаааа eax, 1; i
movаааа ebx, [ebp+8]; array
movаааа edx, [ebp+16]; len
@@fori:
pushаааа ebp
movаааа esi, [ebx+eax*4]; r1
movаааа edi, [edx+eax*4]; r2
movаааа ecx, eax; j
@@forj:
decаааааа ecx
jsааааааааа short @@breakj
cmpаааа [edx+ecx*4], edi
jaeаааааа short @@breakj
movаааа ebp, [edx+ecx*4]
movаааа [edx+ecx*4+4], ebp
movаааа ebp, [ebx+ecx*4]
movаааа [ebx+ecx*4+4], ebp
jmp @@forj
@@breakj:
movаааа [ebx+ecx*4+4], esi
movаааа [edx+ecx*4+4], edi
popааааа ebp
incаааааа eax
cmpаааа eax, [ebp+12]
jbeаааааа @@foriааа аааааа ааа
popad
popааааа ebp
retааааааа 12
endp
end begin
+сЁрЄшЄх тэшьрэшх эр шёяюы№чютрэшх ьхЄюъ тшфр @@шь . ¦Єю Єръ эрчvтрхьvх ыюъры№эvх ьхЄъш, юэш шёяюы№чє¦Єё фы юуЁрэшўхэш юсырёЄш фхщёЄтш ьхЄюъ. +сырёЄ№ фхщёЄтш ьхЄъш Ц ЇєэъЎш шыш юсырёЄ№ ьхцфє фтєь юсvўэvьш ьхЄърьш.
¦Ёш ёюЁЄшЁютъх ьхэ ¦Єё ьхёЄрьш эх ёрьш ёЄЁюъш, р шї рфЁхёр ш фышэv. ¦Єю ьюцхЄ сvЄ№ чэрўшЄхы№эю ¤ЇЇхъЄштэхщ юсьхэр ьхёЄрьш ёЄЁюъ, хёыш юэш фышээvх.
а
Tръ ъръ яЁшэЎшяv ЁрсюЄv ё фтєїьхЁэvьш ш ьэюуюьхЁэvьш ьрёёштрьш юфшэръютv, ёюёЄртыхэшх ЇєэъЎшщ фы ьэюуюьхЁэvї ьрёёштют тvяюыэ хЄё яю Єхь цх яЁртшырь, ўЄю фы фтєїьхЁэюую ьрёёштра
Передача в списке параметров структур
Пример 1. Составить главную программу и функцию для вычисления суммы двух комплексных чисел.
p686
IDEAL
MODEL flat
extrn ExitProcess:proc
DATASEG
struc complex
real dd ?
im dd ?
ends
n1 complex <1, 1>
n2 complex <2, 2>
n3 complex ?
CODESEG
begin:
push [n1.im]
push [n1.real]
push [n2.im]
push [n2.real]
push offset n3
call csum
call ExitProcess
proc csum
push ebp
mov ebp, esp
push eax ebx
mov eax, [(complex ebp+20).real]
add eax, [(complex ebp+12).real]
mov ebx, [ebp+8]
mov [(complex ebx).real], eax
mov eax, [(complex ebp+20).im]
add eax, [(complex ebp+12).im]
mov [(complex ebx).im], eax
pop ebx eax ebp
ret 20
endp csum
end begin
Обратите внимание на запись компонентов комплексного числа в стек. Это число записывается так, чтобы младшая часть числа находилась в младших адресах стека.
Аналогично со структурами используются объединения.
Передача в списке параметров функций
Функции передаются, если необходимо решить класс задач, которые различаются только списком используемых функций, причем эти функции имеют одинаковый список параметров. Пример – вычисление определенного интеграла для разных подинтегральных функций.
Для функций передаются их адреса. Для обращения к функции, заданной своим адресом, используется косвенный вызов.
Пример . Составить главную программу и функции вычисления:
Min – для вычисления минимального из двух заданных целых чисел;
Max – для вычисления максимального из двух заданных целых чисел;
MinMax - для вычисления минимального или максимального из двух заданных целых чисел, в зависимости от заданной в списке параметров функции;
ideal
p686
model flat
extrn ExitProcess:proc
dataseg
x dd 5
y dd 3
z1 dd ?; min
z2 dd ?; max
array dd Min, Max
codeseg
begin:
push [x] [y]
mov eax, [array]; min
push eax
call MinMax
mov [z1], eax
push [x] [y]
mov eax, [array+4]; min
push eax
call MinMax
mov [z2], eax
call ExitProcess; ebp 0
proc Min; eip 4
push ebp; y 8
mov ebp, esp; x 12
mov eax, [ebp+12]
cmp eax, [ebp+8]
jl short @@m1
mov eax, [ebp+8]
@@m1:
pop ebp
ret 8
endp
; ebp 0
proc Max; eip 4
push ebp; y 8
mov ebp, esp; x 12
mov eax, [ebp+12]
cmp eax, [ebp+8]
jg short @@m1
mov eax, [ebp+8]
@@m1:
pop ebp
ret 8
endp
; ebp 0
proc MinMax; eip 4
push ebp; &fun 8
mov ebp, esp;
; y 12
push [dword ebp+16] [dword ebp+12]; x 16
mov eax, [ebp+8]
call eax
pop ebp
ret 12
endp
end begin
Классификация параметров. Способы передачи параметров процедурам
Параметры делятся на параметры исходные данные и параметры результаты. Для параметров первого типа задаются их значения, для параметров второго - адреса. Для передачи параметров используются регистры, стек или другая общая для вызывающей и вызываемой программ область памяти.
Составление функций с переменным списком параметров
Пример. Составить главную программу и функцию для вычисления максимального числа для одного, двух и более чисел.
p586
IDEAL
MODEL fLat
extrn ExitProcess:proc
DATASEG
x dd 1
y dd 2
z dd 3
u dd 4
res dd ?
CODESEG
begin:
push [u] [z] [y] [x] 4; ebp 0
call max; eip 4
add esp, 5*4; 4 8 ; Постоянные
mov [res], eax; [x] 12
call ExitProcess; [y] 16; Переменные
proc max; [z] 20
push ebp; [u] 24
mov ebp, esp;
push ebx ecx edx
mov eax, [ebp+12]; x
mov ecx, [ebp+8]; n
dec ecx
jecxz @@m2
lea ebx, [ebp+16]; & переменной части списка
@@for1:
mov edx, [ebx]
cmp eax, edx
jge short @@m1
mov eax, edx
@@m1:
add ebx, 4
loop @@for1
@@m2:
pop edx ecx ebx ebp
ret
endp
end begin
Обратите внимание на:
1. Формирование адреса начала переменной части списка;
2. Порядок передачи параметров в стек – начинаем с переменных параметров, чтобы постоянные параметры были записаны с постоянным смещением в вершине стека;
3. очистку стека, которую выполняет вызывающая программа, т.к. только она «знает», сколько передала параметров;
Составление рекурсивных функций на ассемблере
Функция называется рекурсивной, если она обращается к самой себе. Рассмотрим составление рекурсивной функции на примере вычисления факториала.
Пример. Составить функцию вычисления Y = N!,
если
Y = 1, если N = 0,
Y = 1 * 2 ... * N, если N > 1
Если N < 0 функция должна возвращать -1, что говорит о неправильном аргументе.
Соответствующая функция на языке С имеет вид:
unsigned fact(int n){
if(n < 0) return -1;
if(n == 0)return 1
return (n * fact(n - 1));
}
;Использование рекурсий в ассемблер программах
ideal
p586
model flat
extrn ExitProcess:proc
dataseg
n dd 4
x dd ?
codeseg
proc fact
push ebp
mov ebp,esp; [ebp+8]-n
push ebx ecx edx
mov ebx, [ebp+8]
test ebx,ebx
js m1; <0
jz m2; ==0
mov ecx, ebx
dec ebx
push ebx
call fact
mul ecx
jmp short m3
m1: mov eax,0
jmp short m3
m2: mov eax,1
m3: pop edx ecx ebx
pop ebp
ret 4
endp fact
begin:
push [n]
call fact
call ExitProcess
end begin
"Прокрутите" программу, чтобы увидеть, как изменяется состояние стека. Вы увидите, что рекурсивные функции требуют, чтобы размер стека был не меньше K * N, где K-число байт стека, используемых при каждом вызове, а N - число вызовов рекурсивной функции
Одномодульные программы
Одномодульная программа может состоять из главной программы (все рассмотренные до сих пор примеры) или из главной программы и процедур (функций).
Использование общих областей памяти
При составлении многомодульных программ программисты часто используют внешние переменные, что позволяет один раз выделить память под переменную и один раз ее инициализировать. Такие переменные определяются в сегменте данных одного из модулей и задаются в директиве PUBLIC этого модуля, а во всех модулях, где используются эти переменные, они определяются в директиве EXTRN.
Пример. Составить функцию вычисления суммы двух чисел, передавая исходные данные через внешние переменные.
; главная программа
ideal
p686
model flat
extrn ExitProcess:proc
extrn Summa:proc
dataseg
y dd 3
x dd 5
z dd ?
public x
public y
codeseg
begin:
call Summa
mov [z], eax
call ExitProcess
end begin
; Функция
ideal
p686
model flat
extrn x:dword, y:dword
codeseg
proc Summa
public Summa;
mov eax, [x]
add eax, [y]
ret
endp
end
Особенности использования внешних функций
Внешняя функция транслируется отдельно от вызывающей программы. Это позволяет не транслировать каждый раз модули, в которых не было изменений. Упрощается изолированная от всей системы отладка отдельных модулей, что допускает участие в разработке системы группы программистов. Не следует заботиться о локальных именах программы, например метках, которые могут совпадать для различных модулей. И, наконец, появляется возможность стыковки разноязыковых модулей.
Чтобы объединить несколько модулей (эту функцию выполняет компоновщик) в одну программу, необходима специальная информация для вызывающей и вызываемой программ. Рассмотрим эту информацию.
В Ы З Ы В А Ю Щ А Я П Р О Г Р А М М А .
1. Необходимо знать, что вызываемый модуль является внешним. Для задания этого этого используется директива:
extrn имя функции : proc
.Директива записывается вне сегментов, в этом случае компоновщик будет искать определение этого имени во всех сегментах, пока не найдет.
2. Если вызывающая программа резервирует память под данные, которые должны использоваться в функции, адреса этих данных должны быть определены как адреса типа public, т.е. доступные(известные) другим сегментам.
Общий вид директивы public:
public имя1, имя2, ....
Директива public, если она необходима, задается в том сегменте, где определено это имя. Директиву можно использовать не только для переменных, но и для меток.
В Ы З Ы В А Е М А Я П Р О Г Р А М М А
1. Имя функции должно быть задано в директиве public
public имя_ функции
2. Если функция использует данные, память под которые выделена в другой программе, то в этой программе используется директива
extrn определение1, определение2,...
Общий вид определения для передаваемых данных:
имя : тип : количество ,
где
имя - имя данного, память под которое выделена в другом модуле;
аааааааааааааааа ; ¦хрышчрЎш рыуюЁшЄьр
аааааааааааааааа ; TюёёЄрэютыхэшх шёяюы№чєхьvї ЁхушёЄЁют
аааааааааааааааааааааааа .........
ааааааааааааааааааааааааааа ret
ааааааааааааааааа шь а endp
аааааааааааааааааааааа end
аааа T ЇєэъЎшш, т юЄышўшх юЄ уыртэющ яЁюуЁрььv, т фшЁхъЄштх end рфЁхё Єюўъш тїюфр эх чрфрхЄё .
аааа +с•шщ тшф тvчvтр¦•хщ яЁюуЁрььv :
аааааааааааааааа .MODELа шь
ааааааааааааааааааааааа ......
аааааааааааааааа extrnа шь _яЁюЎ : proc
ааааааааааааааааааааааа ......
аааааааааааааааа .CODE
аааааааа begin:
аааааааааааааааааааааааа .......
ааааааааааааааааа endа begin
ааа -шЁхъЄштv public ш extrn т ¤Єшї яЁюуЁрььрї ьюцэю чрьхэшЄ№ фшЁхъЄштющ global шь _яЁюЎ:proc. =ю ьv тёх-Єръш Ёхъюьхэфєхь трь шёяюы№чютрЄ№ фшЁхъЄштv public ш extrn,а Є.ъ.а юэша яючтюы ¦Єа сюыхх уыєсюъю шчєўшЄ№ ьхїрэшчь ёт чш ьюфєыхщ. Lьхээю ¤Єш фшЁхъЄштv эршсюыхх ўрёЄю шёяюы№чє¦Єё т юяєсышъютрээvї яЁюуЁрььрї. ¦юёых Єюую, ъръ тv єтхЁхээю яюўєтёЄтєхЄх ёхс т шёяюы№чютрэшш ¤Єшїа фшЁхъЄшт, ьюцэю шї чрьхэшЄ№ фшЁхъЄштющ global.
аааа -ы ёючфрэш шёяюыэ хьющ яЁюуЁрььvа шча эхёъюы№ъшїа ьюфєыхщ, тvяюыэ хЄё Ёрчфхы№эр ЄЁрэёы Ўш фы ърцфюую ьюфєы :
ааааааааааааааааа Tasm32 ./ml /ziа шь _ьюфєы 1
ааааааааааааааааа Tasm32 /ml /ziа шь _ьюфєы 2
ааааааааааааааааа ...
ш ъюьяюэютър тёхї ьюфєыхщ тьхёЄх:
ааааааааааааааааа tlink32 /v шь _ьюфєы 1 шь _ьюфєы 2 ....import32.lib
аааа -ы ЇюЁьшЁютрэш юЄырфюўэющ шэЇюЁьрЎшша шёяюы№чє¦Єё а Єха цх ъы¦ўш, ўЄю ш фы юфэюьюфєы№эvї яЁюуЁрьь.
аааа ¦ЁшьхЁv ёюёЄртыхэш ш шёяюы№чютрэш тэх°эшїа ЇєэъЎшщ.
аааа ¦ЁшьхЁ 1. TюёЄртшЄ№ тэх°э¦¦ ЇєэъЎш¦ фы тvўшёыхэш а z=x+y.
¦рЁрьхЄЁv яхЁхфртрЄ№ ўхЁхч ёЄхъ.
;Tэх°э ЇєэъЎш фы тvўшёыхэш z=x+y. ¦рЁрьхЄЁv
;ааааааааааааааа яхЁхфр¦Єё ўхЁхч ёЄхъ.
;+ыртэр яЁюуЁрььр
; Їрщы main.asm
ideal
p686
model flat
extrn ExitProcess:proc
extrn Summa:proc
dataseg
xааааааааа ddааааааа 5
yааааааааа ddааааааа 3
zаааааааааа ddааааааа ?
codeseg
begin:
pushаааа [x] [y]
callааааа Summa
movаааа [z], eax
call ExitProcess
end begin
; Lрщы PROC.asm
ideal
p586
modelаа flat
codeseg
procа _Summa
public _Summa
arg x:dword, y:dword
pushаааа ebp
movаааа ebp, esp
MOVаа EAX, [x]
addааааа eax, [y]
popааааа ebp
retааааааа
endp
end
аааа -ы ЄЁрэёы Ўшш ¤Єшї Їрщыют шёяюы№чє¦Єё ъюьрэфv
аааааааааааааааааааааааа Tasm32 /zi /mlа main
аааааааааааааааааааааааа Tasm32 /zi /mlа proc
аааааааааааааааааааааааа Tlink32 /vа main proc import32.lib,
т Ёхчєы№ЄрЄх тvяюыэхэш ъюЄюЁvї яюыєўшь Їрщы main.exe.
Реентерабельное программирование
При обычном использовании общих областей проблем не возникает, так как большинство программ выполняется последовательно, одна за другой. Но, что произойдет, если попытаться выполнить одну и ту же функцию более одного раза и в одно и то же время?
Когда это может произойти?
По крайней мере, это возможно в трех случаях. Во-первых, мультизадачные системы могут иметь множество выполняемых программ, разделяющих общие библиотеки программ, (dll). Вместо наличия нескольких копий этих библиотек имеется только одна копия. Для правильной работы библиотеки должны иметь отдельные области данных, чтобы избежать неумышленное совместное использование и порчу данных. Второй случай, когда одна и та же функция может быть вызвана программами одновременно, происходит в системах управления прерываниями. Допустим, что выполняется некоторая программа и произошло прерывание из-за некоторого внешнего события. Программа, обслуживающая прерывание, начинает выполнение и ей необходимо вызвать программу, которая была прервана. Если она не имеет отдельной области данных, то программа обслуживания прерывания разрушит данные, относящиеся к прерванной программе. По этой причине программам обслуживания прерываний необходимо иметь отдельные области данных.
Третий случай использования отдельных областей данных происходит тогда, когда программе необходимо вызвать саму себя. (рекурсия).
Для написания повторно входимых (реентерабельных) программ необходимо, чтобы все промежуточные данные записывались в локальные области памяти. Этому требованию должны удовлетворять все библиотеки, которые могут использоваться в многопоточном режиме.
Использование локальных областей памяти
Для выделения локальной области используется стек.
Структура стека с локальной областью:
; Сохранение регистров
; Локальная область памяти
EBP
EIP
; Фактические параметры
Для выделения локальной области используется команда:
SUB ESP, Размер локальной области
Для освобождения локальной области используется 2 способа:
ADD ESP, Размер локальной области
MOV ESP, EBP
Последний способ более защищен от ошибок, связанных с асинхронным использованием стека
Структура функции с локальной областью:
Proc Имя
Push EBP
Mov EBP, ESP
SUB ESP, Размер локальной области
PUSH Регистры
…
POP Регистры
MOV ESP, EBP
POP EBP
RET [Константа]
Пример. Составить главную программу и функцию для преобразования числа в символьное представление.
Алгоритм.
void itoa (unsigned x){
int numbers[] = {10000, 1000, 100, 10, 1};
for (int I=0; I<5; I++){
Y[I]= x/numbers[i] + ‘0’;
X%= numbers[i];
}
}
ideal
p586
model flat
codeseg
proc itoa; ebp 0
public itoa; eip 4
;; y 8
;; x 12
push ebp
mov ebp, esp
sub esp, 5*4
push eax ebx ecx edx esi
mov [numbers], 10000
mov [numbers+4], 1000
mov [numbers+8], 100
mov [numbers+12], 10
mov [numbers+16], 1
mov ecx, 5
mov eax, [ebp+12]
mov ebx, [ebp+8]
lea esi, [numbers]
for1:
xor edx, edx
div [dword ptr esi]
add al, '0'
mov [ebx], al
inc ebx
add esi, 4
mov eax, edx
loop for1
mov [byte ptr ebx], 0
pop esi edx ecx ebx eax
mov esp, ebp
pop ebp
ret 8
endp itoa
end
Составьте самостоятельно главную программу для этого примера и проверьте ее на машине!
Особенности использования команд ENTER и LEAVE
Анализ функций, составленных ранее, показывает, что в функции требуется:
· Сохранить и сформироваить значение регистра EBP;
· Выделить локальную память.
Для обеспечения этих операций можно использовать команду ENTER (вход), а для автоматического освобождения локальной области и восстановления регистра EBP – команду LEAVE (выход).
Общий вид команды ENTER:
ENTER оп1, оп2
Оп1- определяет размер локальной области в байтах (константное выражение);
Оп2 – уровень вложенности функций (Константное выражение 0 .. 31). Для каждого уровня вложенности копируется фрейм стека при создании нового фрейма. Это делается для обеспечения возможности доступа к параметрам и локальной области внешних функций.
Команда ENTER , если она используется, является первой командой функции, а команда LEAVE – непосредственно перед командой RET.
Если уровень вложенности равен 0, то вставляются команды:
PUSH EBP
MOV EBP, ESP
SUB ESP, оп1.
Для уровня вложенности один или больше, процессор сохраняет указатели на фреймы стеков для предыдущих уровней (содержимое регистра EBP для всех предыдущих уровней).
Команда LEAVE выполняет действия, обратные действиям ENTER, в том числе восстановление регистра EBP.
Директива ARG
Используется для задания имен и типов формальных параметров. Параметры записываются, начиная с вершины стека. Общий вид директивы:
ARG параметр1, параметр2, … [= переменная]
Параметры задаются в виде:
Имя:Тип[:Количество]
В качестве имени используется любое уникальное имя.
Тип может быть стандартным и нестандартным. В качестве стандартных используются типы: BYTE, WORD, DWORD, PWORD, FWORD, QWORD, TBYTE. В качестве нестандартных можно задавать типы структур и объединений.
Количество задается константным выражением и определяет количество элементов данного типа. Используется, если передается массив, а не его адрес. По умолчанию Количество
= 1. Переменной, заданной в директиве присваивается размер области параметров в стеке. Это значение вычисляется компилятором и используется в команде RET.
Пример. Составить функцию для вычисления значения Y=A*X+B для данных длиной 32 бита. Результат длиной 4 байта
; Главная программа
ideal
p686
model flat
extrn ExitProcess:proc
extrn Fun:proc
dataseg
a dd 3
x dd 5
b dd 7
y dd ?
codeseg
begin:
push [a] [x] [b]
call Fun
mov [y], eax
call ExitProcess
end begin
; Функция
ideal
p686
model flat
codeseg
proc Fun
public Fun
arg b:dword, x:dword, a:dword = z
push ebp
mov ebp, esp
mov eax, [a]
mul [x]
add eax, [b]
pop ebp
ret z
endp
end
Директива LOCAL
Директива используется для определения структуры локальной области и ее размера. Сама локальная область не создается. Для ее создания необходима команда SUB ESP, Размер или команда ENTER.
Общий вид директивы:
LOCAL Параметр1, Параметр2,… = Переменная
Локальные параметры задаются точно также, как формальные параметры, т.е. задается имя, тип и, возможно, количество. Размер локальной области присваивается на этапе компиляции переменной, заданной в конце директивы . Переменная используется в командах выделения локальной области.
Пример. Составить главную программу и функцию на ассемблере
int Fun (int x){
int a, b[2];
a=x; b[0]=b[1] = 2 *x;
return a+ b[0] + b[1];
}
; Главная программа
;Int Fun (int x){
; Int a, b[2];
; a=x; b[0]=b[1] = 2 *x;
; return a+ b[0] + b[1];
;}
ideal
p686
model flat
extrn ExitProcess:proc
extrn Fun:proc
dataseg
x dd 5
y dd ?
codeseg
begin:
push [x]
call Fun
mov [y], eax
call ExitProcess
end begin
; Функция
;Int Fun (int x){
; Int a, b[2];
; a=x; b[0]=b[1] = 2 *x;
; return a+ b[0] + b[1];
;}
ideal
p686
model flat
codeseg
begin:
proc Fun
public Fun
arg x:dword = z1
local a:dword, b:dword:2=z2
push ebp
mov ebp, esp
sub esp, z2
mov eax, [x]
mov [a], eax
mov [b], eax
add [b], eax
mov [b+4], eax
add [b+4], eax
mov esp, ebp
pop ebp
ret z1
endp
end
Вставка в Си команд на ассемблере
Рекомендации и правила записи вставок.
1. Команда должна начинаться с ключевого слова asm. При использовании С++
несколько подряд идущих команд могут быть объединены в блок, например
asm {
.......
}
Некоторые версии трансляторов с языка СИ требуют слово asm перед каждой командой или вместо этого слова используют _asm или др.
2. Вставлять можно все допустимые машинные команды и директивы, не определяющие работу транслятора. Так не допускаются директивы
segment и ends ;
proc и endp;
assume и end
и др.
3. В ассемблерной части программы допускается использование данных Си программы всех классов (локальные, внешние, статические и регистровые). Данные могут быть объявлены в ассемблерной части программы, как внешние переменные для использования в ассемблерной части программы. Эти переменные операторы языка «С» не видят, например:
#include <iostream.h>
int y;
asm {
x dd ?
}
int main(int argc, char* argv[])
{
asm{
mov [x], 5
mov eax,[x]
mov [y],eax
}
cout << y;
char cc;
cin >> cc;
return 0;
}
Здесь показано как использовать данные, определенные в участках программы на С и на ассемблере.
4. Для некоторых версий языка С[15], если в ассемблерной части есть команда перехода, то метка записывается по правилам Си, например:
asm jmp label
............
label:asm xor ax, ax
5. Комментарии записывают по правилам Си. Символ ';' используется в качестве конца команды, а не начала комментария. В строке может быть записано несколько команд, например :
asm {push ax; push bx; push cx}
После последней или единственной команды символ ';' можно не ставить. Команды должны быть написаны по правилам MASM, не IDEAL.
6. В ассемблерной части программы нельзя изменить содержимое сегментных регистров, регистров для работы со стеком (esp, ebp), а также регистров esi, edi, если используются регистровые переменные[16].
7. Если встречается хотя бы одна ассемблерная вставка, необходим дополнительный шаг трансляции с языка Си на ассемблер. Для задания транслятору режима перевода в ассемблерный код используется директива
#pragma inline
Если директивы нет, возможна повторная трансляция, если встречена команда на ассемблере.
Ниже приведен пример программы с ассемблерными вставками
#pragma inline
#include <stdio.h>
int summa (int x, int y){
int z;
asm {
mov eax, [x]
add eax, [y]
mov [z], eax
}
return z;
}
main(){
printf(" сумма = % d\n", summa(5, 3));
}
Достоинство метода: наиболее прост с точки зрения программиста.
Недостаток метода. Нельзя отдельно оттранслировать ассемблерную часть.
Использование отдельных функций на ассемблере
Функция должна удовлетворять соглашениям по вызову для языка С++.
Соглашения по вызову различаются по следующим параметрам:
· Правила формирования внутреннего имени функции;
· Правилами передачи параметров;
· Правилами очистки стека.
При формировании имени возможен регистро – чувствительный или регистро нечувствительный режим, возможно формирование дополнительных символов для обеспечения свойства перегрузки функции.
Для обеспечения возможности использования переменного списка параметров параметры могут записываться в стек, начиная с конца списка, для обеспечения максимальной скорости работы – для передачи параметров используются регистры
Очистку стека может выполнять вызывающая программа или функция.
В табл. 11.4 представлены соглашения по вызову и свойства этих соглашений.
Если файл с программой имеет расширение CPP, то по умолчанию используется режим 1, если файл с расширением С++ - режим 2. Если в программе с расширением СPP необходимо использовать функцию с соглашением С, заголовок этой функции имеет вид:
Extern “C” заголовок;
Если необходимо задать заголовочный файл, который можно использовать в программе на С и СРР, то применяют директивы
#ifdef __cplusplus
extern “C” {
#endif
заголовок 1;
заголовок 2;
…
#ifdef __cplusplus
}
#endif
Пример. Составить вызывающую программу и функции вычисления разности для двух чисел, используя все возможные соглашения по вызову.
Таблица 11.4. Соглашения по вызову, принятые в С++
№
п/п | Тип соглашения | Как задается | Внутреннее имя функции | Порядок передачи параметров | Очистка стека | Переменный список параметров | |||||||
1. | Cpp | - | @имя$q[17]… | ¬ | Вызывающая программа | Да | |||||||
2. | С | Extern “C”… | _имя | ¬ | Вызывающая программа | Да | |||||||
3. | Fastcall | Тип __fastcall … | @имя$qqr… | EAX, EDX, ECX | Вызывающая программа | Нет | |||||||
4. | Stdcall | Тип
__stdcall … | @имя$qqs… | ¬ | Функция | Нет | |||||||
5. | Pascal | Тип
__pascal … | @ИМЯ$Q… | ® | Функция | Нет |
// +ыртэр яЁюуЁрььр
#pragma hdrstop
#include <condefs.h>
#include <stdio.h>
//---------------------------------------------------------------------------
USEASM("std.asm");
USEASM("std1.asm");
USEASM("std2.asm");
USEASM("std3.asm");
USEASM("std4.asm");
//---------------------------------------------------------------------------
#pragma argsused
int __stdcallа substract ( int x, int y, int *z);
intаа substract1 (int x, int y, int *z);
intаа __fastcall substract2 (int x, int y, int *z);
extern "C" intаа substract3 (int x, int y, int *z);
intаа pascal substract4 (int x, int y, int *z);
intаа __fastcall sum (int *x, int y);
int main(int argc, char **argv)
{
ааааа int z;
ааааа substract (3, 2, &z); printf ("%d\n", z);
ааааа substract1 (3, 2, &z); printf ("%d\n", z);
ааааа substract2 (3, 2, &z); printf ("%d\n", z);
ааааа substract3 (3, 2, &z); printf ("%d\n", z);
ааааа substract4 (3, 2, &z); printf ("%d\n", z);
ааааа getchar ();
ааааааа return 0;
}
; stdcall
ideal
p586
model flat
codeseg
procааа @substract1$qiipi
public @substract1$qiipi
argаа a:dword, b:dword, c:dword=s
pushааа ebp
movаааа ebp, esp
pushааа ebx
movаааа eax, [a]
subаааа eax, [b]
movаааа ebx, [c]
movаааа [ebx], eax
popаааа ebx ebp
retаааа
endp
end
;fastcall
ideal
p586
model flat
codeseg
procааа @substract2$qqriipi
publicа @substract2$qqriipi
pushааа esi
movаааа esi, eax
subаааа esi, edx
movаааа [ecx], esi
popаааа esi
retаааа
endp
end
а
;extern C
ideal
p586
model flat
codeseg
procааа _substract3
publicа _substract3
argаа a:dword, b:dword, c:dword=s
pushааа ebp
movаааа ebp, esp
pushааа esi
movаааа esi, [a]
subаааа esi, [b]
movаааа ecx, [c]
movаааа [ecx], esi
popаааа esi ebp
ret
endp
end
; pascal
ideal
p586
model flat
codeseg
proc @SUBSTRACT4$QIIPI
public @SUBSTRACT4$QIIPI
argаа c:dword, b:dword, a:dword=s
pushааа ebp
movаааа ebp, esp
pushааа esi
movаааа esi, [a]
subаааа esi, [b]
movаааа ecx, [c]
movаааа [ecx], esi
popаааа esi ebp
retаааа s
endp
end
Стыковка с языками высокого уровня
Существует два способа стыковки:
1. Вставка в Си программу команд на ассемблере;
2. Программа на Си вызывает функцию на ассемблере;
Особенности создания функций для включения их в DLL
Для включения функции в DLL в качестве экспортируемых функций директива PUBLIC должна быть заменена директивой PUBLICDLL.
Пример. Создать экспортируемую функцию для вычитания целых чисел, которая должна входить в DLL как экспортируемая функция. Пусть функция удовлетворяет соглашению по вызову extern “C”:
Файл с функцией proc.asm
;extern C
ideal
p586
model flat
codeseg
proc _substract3
publicdll _substract3
arg a:dword, b:dword, c:dword=s
push ebp
mov ebp, esp
push esi
mov esi, [a]
sub esi, [b]
mov ecx, [c]
mov [ecx], esi
pop esi ebp
ret
endp
end
Главная программа, использующая функцию substract3:
#pragma hdrstop
#pragma argsused
#include <iostream.h>
extern "C" __declspec (dllimport) int substract3 (int, int, int *);
int main(int argc, char* argv[])
{
int z;
substract3 (5, 3, &z);
cout << z;
char c;
cin>>c;
return 0;
}
//---------------------------------------------------------------------------
К проекту долженбыть поключен файл с расширением LIB, соответствующий сформированной DLL.
Аналогично можно использовать DLL библиотеку в режиме загрузки с помощью функции Load Library.