гос / sp-lect (1)
.pdfxor esi,esi mov ax,[ebx] mov edi,esi
mov ecx, lengthof x dec ecx
inc esi
@@: cmp word ptr [ebx+esi*2],ax cmovg ax,word ptr [ebx+esi*2] cmovg edi,esi
inc esi loop @B ret
end start
4.2Багатовимірні масиви
Уякості багатовимірних масивів у більшості практичних задач зустрічаються матриці. При класичній інтерпретації матриці як масиву одновимірних масивів обробка матриці виконується за рядками. При вирішенні деяких задач обробки матриць можна обійтися лінійним циклом, але в більшості випадків використовуються вкладені цикли.
Для адресації елементів матриці використовують базову індексну адресацію з масштабуванням. Для адресації рядків матриці у зовнішньому циклі використовується базовий регістр в який спочатку завантажується адреса початку першого рядка. Подальше переміщення за рядками здійснюється збільшенням значення базового регістру на загальний розмір всіх елементів рядка.
Елементи в рядку адресуються за зміщенням від значення адреси початку рядка в базовому регістрі. Значення зміщення зберігається в індексному регістрі і змінюється у внутрішньому циклі. При використанні масштабних коефіцієнтів (1,2,4,8 в залежності від розміру елементу) значення в індексному регістрі змінюється у внутрішньому циклі від 0 до С-1, де С – кількість елементів рядка (стовпчиків матриці). Така адресація дозволяє оперувати вже
не адресами елементів рядка, а логічними індексами 0,1,2,..., що відповідає нашим інтуїтивним уявленням і використовується мовами програмування високого рівня.
Приклад 4.5 Визначення суми елементів кожного рядка матриці. Результати зберігаються в одновимірному масиві result. Перший елемент масиву result – сума елементів першого рядка матриці, другий елемент масиву result – сума елементів другого рядка матриці, і.т.д.
.386 |
|
|
|
|
.model flat, stdcall |
|
|
|
|
.data |
|
|
|
|
matrix |
dw 1,5,3,2,7 |
|
|
|
cols |
=($-matrix)/type matrix |
; кількість стовпчиків |
||
|
dw 2,5,7,8,9 |
|
|
|
|
dw 4,9,3,2,6 |
|
|
|
rows |
= ($-matrix)/(cols*type matrix) |
; кількість рядків |
||
.data? |
|
|
|
|
result |
dd rows dup (?) |
|
; масив результатів |
|
.code |
|
|
|
|
start: |
|
|
|
|
lea edi,result |
; в edi – |
адреса результату внутрішнього циклу |
||
lea ebx,matrix |
; в ebx – |
адреса першого рядка |
||
mov ecx,rows |
; кількість рядків – лічильник зовнішнього циклу |
|||
rows_cycle: |
|
|
|
|
push ecx |
; зберегти лічильник зовнішнього циклу |
|||
xor eax,eax |
; в dx:ax буде накопичуватися сума елементів |
|||
xor edx,edx |
; у внутрішньому циклі |
|||
xor esi,esi |
; починаючи з першого (зміщення від бази 0) |
|||
mov ecx,cols |
; для всіх елементів рядка |
cols_cycle:
add ax,word ptr [ebx+esi*type matrix] ; додати елемент до суми
adc dx,0 ; з врахуванням можливого переносу
inc esi |
; і перейти до наступного |
loop cols_cycle |
|
shl edx,16 |
; скласти з пари dx:ax єдине 32-х бітне |
or eax,edx |
; в регістрі eax |
mov dword ptr [edi],eax; і зберегти результат
add edi,type result ; перерахувати адресу наступного результату add ebx, cols*type matrix ; перерахувати адресу наступного рядка pop ecx ; й перейти до його обробки
loop rows_cycle ; якщо він не останній
ret end start
Обробка матриці за стовпчикам не значно відрізняється від обробки матриці за рядкам. Змінюється лише порядок вкладення циклів і перерахунок складових адрес. При обробці елементів матриці за стовбцями кількість повторень зовнішнього циклу визначається кількістю стовбців матриці (кількістю елементів у рядку), і стовбці адресуються за допомогою базового регістра, початкове значення якого встановлюється рівним адресі початку першого рядка матриці і збільшується на розмір елемента на кожній ітерації зовнішнього циклу. Кількість повторень внутрішнього циклу визначається кількістю рядків матриці, і рядки адресуються за допомогою індексного регістра шляхом його збільшення на загальний розмір елементів рядка від нуля на кожній ітерації внутрішнього циклу.
Приклад 4.6 Визначення суми елементів кожного стовпчика матриці. Результати роботи зберігаються в одновимірному масиві result. Перший елемент масиву result – сума елементів першого стовпчика матриці, другий елемент масиву result – сума елементів другого стовпчика матриці, і.т.д.
.386
.model flat, stdcall
.data |
|
matrix |
dw 1,5,3,2,7 |
cols |
=($-matrix)/type matrix |
|
dw 2,5,7,8,9 |
|
dw 4,9,3,2,6 |
rows |
= ($-matrix)/(cols*type matrix) |
.data? |
|
result |
dd cols dup (?) |
.code |
|
start: |
|
lea edi,result lea ebx,matrix mov ecx,cols
cols_cycle: push ecx
xor eax,eax xor edx,edx xor esi,esi mov ecx,rows
rows_cycle:
add ax,word ptr [ebx+esi] adc dx,0
add esi,cols*type matrix loop rows_cycle
shl edx,16 or eax,edx
mov dword ptr [edi],eax add edi,type result
add ebx,type matrix pop ecx
loop cols_cycle ret
end start
4.3 Рядки символів
Рядки символів являють собою звичайні одновимірні масиви, однак у зв’язку з деякими особливостями подання, їх виділяють в окрему категорію. Рядки символів розглядають як ланцюги байт або слів довільної довжини. Ознакою кінця ланцюга байт виступає нульовий байт (не символ «0» з ASCII- кодом 30h, а двійковий 0), а ознакою кінця ланцюга слів – нульове слово (два нульових байта). Однобайтові рядки містять коди символів у певному кодуванні, а рядки слів використовують двобайтове кодування Unicode з різним порядком зберігання байтів у слові.
Рядки символів можуть бути оброблені як звичайні одновимірні масиви байтів або слів, однак для вирішення більшості типових задач більш зручним виявляється використання команд обробки ланцюжків у поєднанні із префіксами повторення.
Приклад 4.7 Цей приклад демонструє виконання декількох операцій з обробки ASCII-рядків:
1.Визначає довжину рядка.
2.Визначає кількість слів в рядку (словом вважається група символів розділених пробілом з кодом 20h)
3.Визначає позицію входження під рядка в рядок.
4.Прибирає пробіли в рядку.
5.Перетворює строкові літери рядка на прописні.
6.Шифрує рядок.
Зробимо попередні зауваження щодо реалізації перетворень символів букв із строкових в прописні та шифрування. Проаналізувавши ASCII таблицю кодувань символів, можна зазначити, що:
а) символи строкових і символи прописних букв займають в ній окремі неперервні діапазони;
б) в межах кожного діапазону символи впорядковано в алфавітному порядку так, що старшій за алфавітом букві відповідає більше значення ASCII-коду;
в) значення ASCII-кодів строкових букв відрізняються від ASCII-кодів відповідних їм прописних букв на фіксоване значення.
На прикладі ASCII-кодів букв «a» та «A» в двійковій системі не важко побачити, що відрізняються ці коди лише значенням одного біта.
«A» – 41h = 0010 0001b
«a» – 61h = 0 110 0001b
Таким чином, перетворення символу зі строкового на прописний виконується скиданням шостого біту байта ASCII-коду, і навпаки, перетворення символу із прописного на строковий виконується встановленням шостого біту байта ASCII-коду.
Найпростіші методи шифрування тексту передбачають виконання над кожним байтом ASCII-коду вихідного повідомлення деякого перетворення в результаті якого цей ASCII-код буде змінено.
Таке перетворення може виконуватися з використанням суперпозиції будь-яких арифметичних та/або логічних операцій, але це перетворення повинно бути зворотнім, тобто передбачати можливість однозначного поновлення шифрованого тексту.
Властивістю зворотності володіє елементарна логічна функція XOR для якої в системі команд процесора передбачена однойменна команда. Наприклад,
0010 0001b XOR 0000 0111b = 0010 0110b – шифрування 0010 0110b XOR 0000 0111b = 0010 0001b – дешифрування
0010 0001b = 41h – «A»
0000 0111b – маска (ключ шифру)
0010 0110b = 46h – «F»
0000 0111b – маска (ключ шифру)
0010 0001b = 41h – «A»
.386
.model flat, stdcall
.data
str1 |
db "uiop",0 |
str2 |
db "qwertyuiopasdfghjkl",0 |
str3 |
db "qw erty uio pasd fgh jkl",0 |
.code |
|
start: |
|
; Знайти довжину рядка str3
lea edi,str3 |
|
mov ecx,-1 |
|
xor al,al |
|
repnz scasb |
|
neg ecx |
|
dec ecx |
; в ECX – довжина рядка str3 |
; Підрахувати кількість слів в str3
lea edi,str3 xor ebx,ebx mov al,20h
@@: repnz scasb jecxz @F
inc ebx ; в EВX – кількість слів
jmp @B
; Знайти входження рядка str1 в str2
@@: mov ecx,lengthof str2-lengthof str1+1
lea ebx,str2
@@: push ecx
mov edi,ebx lea esi,str1
mov ecx,lengthof str1
repe cmpsb |
|
jecxz @F |
|
inc ebx |
|
pop ecx |
|
loop @B |
|
jmp NotFound |
|
@@: pop ecx |
; в EВX – адреса початку підрядка str1 в рядку str2 |
lea eax,str2
sub ebx,eax ; в EВX – позиція (з нуля) підрядка str1 в рядку str2
NotFound:
; Прибрати пробіли в str3
lea esi,str3 mov edi,esi
@@: lodsb or al,al jz @F
cmp al,20h je @B stosb
jmp @B @@: stosb
; Перетворити строкові літери в str3 на прописні
lea esi,str3 lea edi,esi
@@: lodsb or al,al jz @F
cmp al,"a" jb @B cmp al,"z" ja @B
or al, 01000000b stosb
jmp @B @@: stosb
; Зашифрувати str3
lea esi,str3
lea edi,esi
@@: lodsb
or al,al
jz @F
xor al,01010101b
stosb
jmp @B
@@: stosb
ret
end start
4.4 Записи
Запис – це структурований тип даних, що складається з фіксованої кількості елементів довжиною від одного до декількох бітів, що називаються бітовими полями. Для використання записів у програмі потрібно спочатку виконати опис типу (шаблону) запису у вигляді:
Ім’я запису RECORD Ім’я поля:довжина[=значення] [,...]
Імена полів запису повинні бути унікальними, довжина полів зазначається у бітах, за необхідності бітовому полю можна призначити не обов’язкове початкове значення ініціалізації за замовченням. Загальна довжина усіх бітових полів запису не може перевищувати 8/16/32/64 біта.
Наприклад,
DATE |
RECORD |
Year:12, Month:4, Day:5 |
DATE |
RECORD |
Year:12=2011, Month:4, Day:5 |
DATE |
RECORD |
Year:12=2011, Month:4=01, Day:5=01 |
Опис типу запису це тільки інформація для транслятора і пам’ять під його розміщення не виділяється, тому опис типу запису виконується до першої директиви визначення секцій даних і констант.
Після опису типу запису на його основі можна визначати одну або декілька змінних у вигляді:
Ім’я змінної Ім’я запису <[значення[,значення]…]> Ім’я змінної Ім’я запису {[значення[,значення]…]}
Ім’я змінної Ім’я запису кількість DUP (<[значення[,значення]…]>) Ім’я змінної Ім’я запису кількість DUP ({[значення[,значення]…]})
Ініціалізація бітових полів запису припустима для секції .data, для секції констант .const її можна вважати обов’язковою, а для секції не ініціалізованих даних .data? вона не припустима (ігнорується з повідомленням транслятора). Змінні з типом запису у секції не ініціалізованих даних .data? визначаються на основі опису типу без попередньої ініціалізації полів і виглядають наступним чином:
.data? |
|
Date1 |
DATE <> |
Date2 |
DATE {} |
Date3 |
DATE 5 DUP ({}) |
Date4 |
DATE 5 DUP (<>) |
Змінні з типом запису у секції ініціалізованих даних .data можуть визначатися як на основі опису типу з попередньою ініціалізацією полів, так і без неї. При визначенні змінної надається можливість прийняти значення попередньої ініціалізації, перевизначити та/або ініціалізувати будь яке одне або усі поля запису. Символом роздільником полів запису виступає кома. Якщо ініціалізації підлягають лише деякі бітові поля, то для зазначення їх місце розташування зберігають порядок роздільників. Наприклад:
Date1 |
DATE 5 dup (<2011,04,24>) |
Date2 |
DATE {2011,,24} |
Date3 |
DATE <2011,04,> |
Date4 |
DATE <2011,04> |
Date5 |
DATE {,,25} |
Date6 |
DATE {} |
Під змінні з типом запису виділяється пам’ять. Розмір наданої для розміщення змінної пам’яті складає 8/16/32/64 біта і визначається загальною довжиною усіх бітових полів запису – обирається найменший розмір достатній для розміщення усіх полів запису. При цьому якщо загальна довжина усіх бітових полів запису не дорівнює 8/16/32/64 бітам, то ліві (старші) біти байта, слова, подвійного або зчетвереного слова не використовуються.