background image

Wydawnictwo Helion
ul. Chopina 6
44-100 Gliwice
tel. (32)230-98-63

e-mail: helion@helion.pl

PRZYK£ADOWY ROZDZIA£

PRZYK£ADOWY ROZDZIA£

IDZ DO

IDZ DO

ZAMÓW DRUKOWANY KATALOG

ZAMÓW DRUKOWANY KATALOG

KATALOG KSI¥¯EK

KATALOG KSI¥¯EK

TWÓJ KOSZYK

TWÓJ KOSZYK

CENNIK I INFORMACJE

CENNIK I INFORMACJE

ZAMÓW INFORMACJE

O NOWOCIACH

ZAMÓW INFORMACJE

O NOWOCIACH

ZAMÓW CENNIK

ZAMÓW CENNIK

CZYTELNIA

CZYTELNIA

FRAGMENTY KSI¥¯EK ONLINE

FRAGMENTY KSI¥¯EK ONLINE

SPIS TRECI

SPIS TRECI

DODAJ DO KOSZYKA

DODAJ DO KOSZYKA

KATALOG ONLINE

KATALOG ONLINE

Asembler.
Sztuka programowania

Ksi¹¿ka „Asembler. Sztuka programowania” to podrêcznik programowania w jêzyku 
asemblera oparty na jêzyku HLA. Opisuje 32-bitow¹ architekturê procesorów Intel
oraz zasady tworzenia programów w jêzyku HLA. Przedstawia sposoby pisania, 
kompilacji i uruchamiania programów wykorzystuj¹cych ró¿ne, nawet najbardziej 
z³o¿one typy danych.

• Wprowadzenie do jêzyka HLA
• Sposoby reprezentacji danych
• Organizacja pamiêci i tryby adresowania
• Typy danych
• Podzia³ programu na procedury i modu³y
• Sterowanie wykonaniem programu
• Instrukcje arytmetyczne
• Operacje na plikach
• Operacje bitowe i ³añcuchowe
• Makrodefinicje
• Klasy i obiekty
• Po³¹czenie asemblera z programami w innych jêzykach

Przekonaj siê, jak prosty jest jêzyk asemblera

Autor: Randall Hyde
T³umaczenie: Przemys³aw Szeremiota
ISBN: 83-7361-602-0
Tytu³ orygina³u

The Art of Assembly Language

Format: B5, stron: 968

background image

Spis treści

Rozdział 1.  Wstęp do języka asemblerowego ...................................................... 13

1.1. Wprowadzenie ........................................................................................................ 13
1.2. Anatomia programu HLA....................................................................................... 13
1.3. Uruchamianie pierwszego programu HLA ............................................................. 16
1.4. Podstawowe deklaracje danych programu HLA..................................................... 17
1.5. Wartości logiczne ................................................................................................... 19
1.6. Wartości znakowe................................................................................................... 20
1.7. Rodzina procesorów 80x86 firmy Intel.................................................................. 21

1.7.1. Podsystem obsługi pamięci........................................................................... 24

1.8. Podstawowe instrukcje maszynowe........................................................................ 26
1.9. Podstawowe struktury sterujące wykonaniem programu HLA............................... 30

1.9.1. Wyrażenia logiczne w instrukcjach HLA ..................................................... 31
1.9.2. Instrukcje if..then..elseif..else..endif języka HLA ......................................... 33
1.9.3. Iloczyn, suma i negacja w wyrażeniach logicznych...................................... 35
1.9.4. Instrukcja while ............................................................................................ 37
1.9.5. Instrukcja for................................................................................................. 38
1.9.6. Instrukcja repeat............................................................................................ 39
1.9.7. Instrukcje break oraz breakif......................................................................... 40
1.9.8. Instrukcja forever.......................................................................................... 40
1.9.9. Instrukcje try, exception oraz endtry............................................................. 41

1.10. Biblioteka standardowa języka HLA — wprowadzenie ......................................... 44

1.10.1. Stałe predefiniowane w module stdio ........................................................... 46
1.10.2. Standardowe wejście i wyjście programu ..................................................... 46
1.10.3. Procedura stdout.newln................................................................................. 47
1.10.4. Procedury stdout.putiN ................................................................................. 47
1.10.5. Procedury stdout.putiNSize .......................................................................... 48
1.10.6. Procedura stdout.put ..................................................................................... 49
1.10.7. Procedura stdin.getc...................................................................................... 51
1.10.8. Procedury stdin.getiN ................................................................................... 52
1.10.9. Procedury stdin.readLn i stdin.flushInput..................................................... 53

1.10.10. Procedura stdin.get ....................................................................................... 54

1.11. Jeszcze o ochronie wykonania kodu w bloku try..endtry........................................ 55

1.11.1. Zagnieżdżone bloki try..endtry ..................................................................... 56
1.11.2. Klauzula unprotected bloku try..endtry......................................................... 58
1.11.3. Klauzula anyexception bloku try..endtry ...................................................... 61
1.11.4. Instrukcja try..endtry i rejestry...................................................................... 61

1.12. Język asemblerowy a język HLA ........................................................................... 63
1.13. Źródła informacji dodatkowych.............................................................................. 64

background image

4

Asembler. Sztuka programowania

Rozdział 2.  Reprezentacja danych ...................................................................... 65

2.1. Wprowadzenie ........................................................................................................ 65
2.2. Systemy liczbowe ................................................................................................... 66

2.2.1. System dziesiętny — przypomnienie............................................................ 66
2.2.2. System dwójkowy......................................................................................... 66
2.2.3. Formaty liczb dwójkowych........................................................................... 68

2.3. System szesnastkowy.............................................................................................. 69
2.4. Organizacja danych ................................................................................................ 72

2.4.1. Bity ............................................................................................................... 72
2.4.2. Półbajty......................................................................................................... 73
2.4.3. Bajty ............................................................................................................. 73
2.4.4. Słowa ............................................................................................................ 75
2.4.5. Podwójne słowa ............................................................................................ 76
2.4.6. Słowa poczwórne i długie............................................................................. 77

2.5. Operacje arytmetyczne na liczbach dwójkowych i szesnastkowych....................... 77
2.6. Jeszcze o liczbach i ich reprezentacji...................................................................... 78
2.7. Operacje logiczne na bitach.................................................................................... 81
2.8. Operacje logiczne na liczbach dwójkowych i ciągach bitów.................................. 84
2.9. Liczby ze znakiem i bez znaku ............................................................................... 86

2.10. Rozszerzanie znakiem, rozszerzanie zerem, skracanie, przycinanie....................... 91
2.11. Przesunięcia i obroty .............................................................................................. 95
2.12. Pola bitowe i dane spakowane ................................................................................ 99
2.13. Wprowadzenie do arytmetyki zmiennoprzecinkowej ........................................... 104

2.13.1. Formaty zmiennoprzecinkowe przyjęte przez IEEE ................................... 108
2.13.2. Obsługa liczb zmiennoprzecinkowych w języku HLA ............................... 112

2.14. Reprezentacja liczb BCD ..................................................................................... 115
2.15. Znaki .................................................................................................................... 117

2.15.1. Zestaw znaków ASCII ................................................................................ 117
2.15.2. Obsługa znaków ASCII w języku HLA...................................................... 121

2.16. Zestaw znaków Unicode....................................................................................... 125
2.17. Źródła informacji dodatkowych............................................................................ 126

Rozdział 3.  Dostęp do pamięci i jej organizacja ................................................ 127

3.1. Wprowadzenie ...................................................................................................... 127
3.2. Tryby adresowania procesorów 80x86 ................................................................. 127

3.2.1. Adresowanie przez rejestr........................................................................... 128
3.2.2. 32-bitowe tryby adresowania procesora 80x86........................................... 129

3.3. Organizacja pamięci fazy wykonania ................................................................... 135

3.3.1. Obszar kodu................................................................................................ 137
3.3.2. Obszar zmiennych statycznych ................................................................... 139
3.3.3. Obszar niemodyfikowalny .......................................................................... 140
3.3.4. Obszar danych niezainicjalizowanych ........................................................ 141
3.3.5. Atrybut @nostorage.................................................................................... 141
3.3.6. Sekcja deklaracji var ................................................................................... 142
3.3.7. Rozmieszczenie sekcji deklaracji danych w programie HLA ..................... 143

3.4. Przydział pamięci dla zmiennych w programach HLA......................................... 144
3.5. Wyrównanie danych w programach HLA ............................................................ 146
3.6. Wyrażenia adresowe............................................................................................. 149
3.7. Koercja typów ...................................................................................................... 151
3.8. Koercja typu rejestru ............................................................................................ 154
3.9. Pamięć obszaru stosu oraz instrukcje push i pop .................................................. 155

3.9.1. Podstawowa postać instrukcji push............................................................. 155
3.9.2. Podstawowa postać instrukcji pop .............................................................. 157
3.9.3. Zachowywanie wartości rejestrów za pomocą instrukcji push i pop .......... 158

background image

Spis treści

5

3.9.4. Stos jako kolejka LIFO ............................................................................... 159
3.9.5. Pozostałe wersje instrukcji obsługi stosu .................................................... 161
3.9.6. Usuwanie danych ze stosu bez ich zdejmowania ........................................ 163
3.9.7. Odwoływanie się do danych na stosie bez ich zdejmowania ...................... 165

3.10. Dynamiczny przydział pamięci — obszar pamięci sterty ..................................... 166
3.11. Instrukcje inc oraz dec.......................................................................................... 171
3.12. Pobieranie adresu obiektu..................................................................................... 171
3.13. Źródła informacji dodatkowych............................................................................ 172

Rozdział 4.  Stałe, zmienne i typy danych .......................................................... 173

4.1. Wprowadzenie ...................................................................................................... 173
4.2. Kilka dodatkowych instrukcji: intmul, bound i into ............................................. 174
4.3. Typ tbyte............................................................................................................... 178
4.4. Deklaracje stałych i zmiennych w języku HLA.................................................... 178

4.4.1. Typy stałych ............................................................................................... 182
4.4.2. Literały stałych łańcuchowych i znakowych............................................... 183
4.4.3. Stałe łańcuchowe i napisowe w sekcji const ............................................... 185
4.4.4. Wyrażenia stałowartościowe....................................................................... 186
4.4.5. Wielokrotne sekcje const i ich kolejność w programach HLA ................... 189
4.4.6. Sekcja val programu HLA .......................................................................... 190
4.4.7. Modyfikowanie obiektów sekcji val w wybranym miejscu

kodu źródłowego programu ....................................................................... 191

4.5. Sekcja type programu HLA .................................................................................. 192
4.6. Typy wyliczeniowe w języku HLA ...................................................................... 193
4.7. Typy wskaźnikowe ............................................................................................... 194

4.7.1. Wskaźniki w języku asemblerowym........................................................... 196
4.7.2. Deklarowanie wskaźników w programach HLA ........................................ 197
4.7.3. Stałe wskaźnikowe i wyrażenia stałych wskaźnikowych............................ 197
4.7.4. Zmienne wskaźnikowe a dynamiczny przydział pamięci ........................... 199
4.7.5. Typowe błędy stosowania wskaźników ...................................................... 200

4.8. Moduł chars.hhf biblioteki standardowej HLA .................................................... 205
4.9. Złożone typy danych ............................................................................................ 207

4.10. Łańcuchy znaków ................................................................................................. 208
4.11. Łańcuchy w języku HLA...................................................................................... 210
4.12. Odwołania do poszczególnych znaków łańcucha ................................................. 217
4.13. Moduł strings biblioteki standardowej HLA i procedury manipulacji łańcuchami ...219
4.14. Konwersje wewnątrzpamięciowe ......................................................................... 231
4.15. Zbiory znaków...................................................................................................... 232
4.16. Implementacja zbiorów znaków w języku HLA................................................... 233
4.17. Literały, stałe i wyrażenia zbiorów znaków w języku HLA ................................. 235
4.18. Operator in w wyrażeniach logicznych

wysokopoziomowego rozszerzenia języka HLA.................................................. 237

4.19. Obsługa zbiorów znaków w bibliotece standardowej HLA .................................. 237
4.20. Wykorzystywanie zbiorów znaków w programach HLA ..................................... 241
4.21. Tablice.................................................................................................................. 243
4.22. Deklarowanie tablic w programach HLA ............................................................. 244
4.23. Literały tablicowe ................................................................................................. 245
4.24. Odwołania do elementów tablicy jednowymiarowej ............................................ 246

4.24.1. Porządkowanie tablicy wartości ................................................................. 248

4.25. Tablice wielowymiarowe ..................................................................................... 250

4.25.1. Wierszowy układ elementów tablicy .......................................................... 251
4.25.2. Kolumnowy układ elementów tablicy ........................................................ 255

4.26. Przydział pamięci dla tablic wielowymiarowych ................................................. 256
4.27. Odwołania do elementów tablic wielowymiarowych w języku asemblerowym... 258

background image

6

Asembler. Sztuka programowania

4.28. Duże tablice i MASM (tylko dla programistów systemu Windows) .................... 259
4.29. Rekordy (struktury) .............................................................................................. 260
4.30. Stałe rekordowe .................................................................................................... 263
4.31. Tablice rekordów.................................................................................................. 264
4.32. Wykorzystanie tablic i rekordów w roli pól rekordów ......................................... 265
4.33. Ingerowanie w przesunięcia pól rekordów ........................................................... 269
4.34. Wyrównanie pól w ramach rekordu...................................................................... 270
4.35. Wskaźniki na rekordy........................................................................................... 271
4.36. Unie ...................................................................................................................... 273
4.37. Unie anonimowe................................................................................................... 275
4.38. Typy wariantowe .................................................................................................. 276
4.39. Stałe unii............................................................................................................... 277
4.40. Przestrzenie nazw ................................................................................................. 278
4.41. Tablice dynamiczne w języku asemblerowym ..................................................... 281
4.42. Obsługa tablic w bibliotece standardowej języka HLA ........................................ 284
4.43. Źródła informacji dodatkowych............................................................................ 287

Rozdział 5.  Procedury i moduły ........................................................................ 289

5.1. Wprowadzenie ...................................................................................................... 289
5.2. Procedury.............................................................................................................. 289
5.3. Zachowywanie stanu systemu .............................................................................. 292
5.4. Przedwczesny powrót z procedury ....................................................................... 296
5.5. Zmienne lokalne ................................................................................................... 297
5.6. Symbole lokalne i globalne obiektów innych niż zmienne ................................... 303
5.7. Parametry.............................................................................................................. 304

5.7.1. Przekazywanie przez wartość ..................................................................... 305
5.7.2. Przekazywanie przez adres ......................................................................... 308

5.8. Funkcje i wartości funkcji .................................................................................... 311

5.8.1. Zwracanie wartości funkcji......................................................................... 312
5.8.2. Złożenie instrukcji języka HLA .................................................................. 313
5.8.3. Atrybut @returns procedur języka HLA..................................................... 316

5.9. Rekurencja............................................................................................................ 318

5.10. Deklaracje zapowiadające .................................................................................... 322
5.11. Procedury w ujęciu niskopoziomowym — instrukcja call.................................... 323
5.12. Rola stosu w procedurach..................................................................................... 325
5.13. Rekordy aktywacji................................................................................................ 328
5.14. Standardowa sekwencja wejścia do procedury ..................................................... 331
5.15. Standardowa sekwencja wyjścia z procedury ....................................................... 333
5.16. Niskopoziomowa implementacja zmiennych automatycznych ............................ 334
5.17. Niskopoziomowa implementacja parametrów procedury..................................... 336

5.17.1. Przekazywanie argumentów w rejestrach ................................................... 337
5.17.2. Przekazywanie argumentów w kodzie programu........................................ 340
5.17.3. Przekazywanie argumentów przez stos....................................................... 342

5.18. Wskaźniki na procedury ....................................................................................... 365
5.19. Parametry typu procedurowego ............................................................................ 368
5.20. Nietypowane parametry wskaźnikowe ................................................................. 370
5.21. Zarządzanie dużymi projektami programistycznymi............................................ 371
5.22. Dyrektywa #include ............................................................................................. 372
5.23. Unikanie wielokrotnego włączania do kodu tego samego pliku ........................... 374
5.24. Moduły a atrybut @external................................................................................. 375

5.24.1. Działanie atrybutu @external..................................................................... 380
5.24.2. Pliki nagłówkowe w programach HLA....................................................... 382

5.25. Jeszcze o problemie zaśmiecania przestrzeni nazw .............................................. 384
5.26. Źródła informacji dodatkowych............................................................................ 386

background image

Spis treści

7

Rozdział 6.  Arytmetyka.................................................................................... 389

6.1. Wprowadzenie ...................................................................................................... 389
6.2. Zestaw instrukcji arytmetycznych procesora 80x86 ............................................. 389

6.2.1. Instrukcje mul i imul................................................................................... 389
6.2.2. Instrukcje div i idiv ..................................................................................... 393
6.2.3. Instrukcja cmp ............................................................................................ 396
6.2.4. Instrukcje setXX ......................................................................................... 401
6.2.5. Instrukcja test.............................................................................................. 403

6.3. Wyrażenia arytmetyczne ...................................................................................... 404

6.3.1. Proste przypisania ....................................................................................... 405
6.3.2. Proste wyrażenia ......................................................................................... 406
6.3.3. Wyrażenia złożone...................................................................................... 408
6.3.4. Operatory przemienne................................................................................. 413

6.4. Wyrażenia logiczne .............................................................................................. 414
6.5. Idiomy maszynowe a idiomy arytmetyczne.......................................................... 417

6.5.1. Mnożenie bez stosowania instrukcji mul, imul i intmul.............................. 417
6.5.2. Dzielenie bez stosowania instrukcji div i idiv............................................. 419
6.5.3. Zliczanie modulo n za pośrednictwem instrukcji and ................................. 420
6.5.4. Nieostrożne korzystanie z idiomów maszynowych .................................... 420

6.6. Arytmetyka zmiennoprzecinkowa ........................................................................ 421

6.6.1. Rejestry jednostki zmiennoprzecinkowej.................................................... 421
6.6.2. Typy danych jednostki zmiennoprzecinkowej ............................................ 429
6.6.3. Zestaw instrukcji jednostki zmiennoprzecinkowej ..................................... 430
6.6.4. Instrukcje przemieszczania danych............................................................. 431
6.6.5. Instrukcje konwersji.................................................................................... 433
6.6.6. Instrukcje arytmetyczne .............................................................................. 436
6.6.7. Instrukcje porównań ................................................................................... 442
6.6.8. Instrukcje ładowania stałych na stos koprocesora....................................... 445
6.6.9. Instrukcje funkcji przestępnych .................................................................. 445

6.6.10. Pozostałe instrukcje jednostki zmiennoprzecinkowej ................................. 447
6.6.11. Instrukcje operacji całkowitoliczbowych.................................................... 449

6.7. Tłumaczenie wyrażeń arytmetycznych na kod maszynowy

jednostki zmiennoprzecinkowej ........................................................................... 449

6.7.1. Konwersja notacji wrostkowej do odwrotnej notacji polskiej..................... 451
6.7.2. Konwersja odwrotnej notacji polskiej do kodu języka asemblerowego...... 453

6.8. Obsługa arytmetyki zmiennoprzecinkowej w bibliotece standardowej języka HLA.....455

6.8.1. Funkcje stdin.getf i fileio.getf ..................................................................... 455
6.8.2. Funkcje trygonometryczne modułu math.................................................... 455
6.8.3. Funkcje wykładnicze i logarytmiczne modułu math................................... 456

6.9. Podsumowanie...................................................................................................... 458

Rozdział 7.  Niskopoziomowe struktury sterujące wykonaniem programu............ 459

7.1. Wprowadzenie ...................................................................................................... 459
7.2. Struktury sterujące niskiego poziomu................................................................... 460
7.3. Etykiety instrukcji................................................................................................. 460
7.4. Bezwarunkowy skok do instrukcji (instrukcja jmp) ............................................. 462
7.5. Instrukcje skoku warunkowego ............................................................................ 465
7.6. Struktury sterujące „średniego” poziomu — jt i jf................................................ 468
7.7. Implementacja popularnych struktur sterujących w języku asemblerowym ......... 469
7.8. Wstęp do podejmowania decyzji .......................................................................... 469

7.8.1. Instrukcje if..then..else ................................................................................ 471
7.8.2. Tłumaczenie instrukcji if języka HLA na język asemblerowy.................... 475
7.8.3. Obliczanie wartości złożonych wyrażeń logicznych

— metoda pełnego szacowania wartości wyrażenia................................... 480

background image

8

Asembler. Sztuka programowania

7.8.4. Skrócone szacowanie wyrażeń logicznych ................................................. 481
7.8.5. Wady i zalety metod szacowania wartości wyrażeń logicznych ................. 483
7.8.6. Efektywna implementacja instrukcji if w języku asemblerowym ............... 485
7.8.7. Instrukcje wyboru ....................................................................................... 490

7.9. Skoki pośrednie a automaty stanów...................................................................... 500

7.10. Kod spaghetti........................................................................................................ 503
7.11. Pętle...................................................................................................................... 504

7.11.1. Pętle while .................................................................................................. 505
7.11.2. Pętle repeat..until ........................................................................................ 506
7.11.3. Pętle nieskończone ..................................................................................... 508
7.11.4. Pętle for ...................................................................................................... 508
7.11.5. Instrukcje break i continue.......................................................................... 509
7.11.6. Pętle a rejestry ............................................................................................ 513

7.12. Optymalizacja kodu.............................................................................................. 514

7.12.1. Obliczanie warunku zakończenia pętli na końcu pętli ................................ 515
7.12.2. Zliczanie licznika pętli wstecz .................................................................... 517
7.12.3. Wstępne obliczanie niezmienników pętli.................................................... 518
7.12.4. Rozciąganie pętli ........................................................................................ 519
7.12.5. Zmienne indukcyjne ................................................................................... 521

7.13. Mieszane struktury sterujące w języku HLA ........................................................ 522
7.14. Źródła informacji dodatkowych............................................................................ 524

Rozdział 8.  Pliki .............................................................................................. 525

8.1. Wprowadzenie ...................................................................................................... 525
8.2. Organizacja plików............................................................................................... 525

8.2.1. Pliki jako listy rekordów............................................................................. 526
8.2.2. Pliki tekstowe a pliki binarne...................................................................... 528

8.3. Pliki sekwencyjne ................................................................................................. 530
8.4. Pliki dostępu swobodnego .................................................................................... 538
8.5. Indeksowany sekwencyjny dostęp do pliku (ISAM) ............................................ 543
8.6. Przycinanie pliku .................................................................................................. 546
8.7. Źródła informacji dodatkowych............................................................................ 548

Rozdział 9.  Zaawansowane obliczenia w języku asemblerowym......................... 549

9.1. Wprowadzenie ...................................................................................................... 549
9.2. Operacje o zwielokrotnionej precyzji ................................................................... 550

9.2.1. Obsługa operacji zwielokrotnionej precyzji

w bibliotece standardowej języka HLA...................................................... 550

9.2.2. Dodawanie liczb zwielokrotnionej precyzji ................................................ 553
9.2.3. Odejmowanie liczb zwielokrotnionej precyzji............................................ 556
9.2.4. Porównanie wartości o zwielokrotnionej precyzji ...................................... 558
9.2.5. Mnożenie operandów zwielokrotnionej precyzji ........................................ 562
9.2.6. Dzielenie wartości zwielokrotnionej precyzji ............................................. 565
9.2.7. Negacja operandów zwielokrotnionej precyzji ........................................... 575
9.2.8. Iloczyn logiczny operandów zwielokrotnionej precyzji.............................. 577
9.2.9. Suma logiczna operandów zwielokrotnionej precyzji................................. 577

9.2.10. Suma wyłączająca operandów zwielokrotnionej precyzji........................... 578
9.2.11. Inwersja operandów zwielokrotnionej precyzji .......................................... 578
9.2.12. Przesunięcia bitowe operandów zwielokrotnionej precyzji ........................ 578
9.2.13. Obroty operandów zwielokrotnionej precyzji............................................. 583
9.2.14. Operandy zwielokrotnionej precyzji w operacjach wejścia-wyjścia........... 583

9.3. Manipulowanie operandami różnych rozmiarów.................................................. 604
9.4. Arytmetyka liczb dziesiętnych.............................................................................. 606

9.4.1. Literały liczb BCD...................................................................................... 608
9.4.2. Instrukcje maszynowe daa i das.................................................................. 608

background image

Spis treści

9

9.4.3. Instrukcje maszynowe aaa, aas, aam i aad .................................................. 610
9.4.4. Koprocesor a arytmetyka spakowanych liczb dziesiętnych ........................ 612

9.5. Obliczenia w tabelach........................................................................................... 615

9.5.1. Wyszukiwanie w tabeli wartości funkcji .................................................... 615
9.5.2. Dopasowywanie dziedziny ......................................................................... 620
9.5.3. Generowanie tabel wartości funkcji............................................................ 621
9.5.4. Wydajność odwołań do tabel przeglądowych ............................................. 625

9.6. Źródła informacji dodatkowych............................................................................ 625

Rozdział 10. Makrodefinicje i język czasu kompilacji .......................................... 627

10.1. Wprowadzenie...................................................................................................... 627
10.2. Język czasu kompilacji — wstęp .......................................................................... 627
10.3. Instrukcje #print i #error....................................................................................... 629
10.4. Stałe i zmienne czasu kompilacji.......................................................................... 631
10.5. Wyrażenia i operatory czasu kompilacji............................................................... 632
10.6. Funkcje czasu kompilacji ..................................................................................... 635

10.6.1. Funkcje czasu kompilacji — konwersja typów........................................... 636
10.6.2. Funkcje czasu kompilacji — obliczenia numeryczne ................................. 638
10.6.3. Funkcje czasu kompilacji — klasyfikacja znaków ..................................... 638
10.6.4. Funkcje czasu kompilacji — manipulacje łańcuchami znaków .................. 639
10.6.5. Funkcje czasu kompilacji — dopasowywanie wzorców............................. 639
10.6.6. Odwołania do tablicy symboli .................................................................... 641
10.6.7. Pozostałe funkcje czasu kompilacji ............................................................ 643
10.6.8. Konwersja typu stałych napisowych........................................................... 643

10.7. Kompilacja warunkowa ........................................................................................ 645
10.8. Kompilacja wielokrotna (pętle czasu kompilacji)................................................. 650
10.9. Makrodefinicje (procedury czasu kompilacji) ...................................................... 653

10.9.1. Makrodefinicje standardowe....................................................................... 654
10.9.2. Argumenty makrodefinicji.......................................................................... 656
10.9.3. Symbole lokalne makrodefinicji ................................................................. 663
10.9.4. Makrodefinicje jako procedury czasu kompilacji ....................................... 666
10.9.5. Symulowane przeciążanie funkcji .............................................................. 667

10.10. Tworzenie programów czasu kompilacji .............................................................. 672

10.10.1. Generowanie tabel wartości funkcji............................................................ 673
10.10.2. Rozciąganie pętli ........................................................................................ 677

10.11. Stosowanie makrodefinicji w osobnych plikach kodu źródłowego ...................... 679
10.12. Źródła informacji dodatkowych ........................................................................... 679

Rozdział 11. Manipulowanie bitami .................................................................... 681

11.1. Wprowadzenie...................................................................................................... 681
11.2. Czym są dane bitowe? .......................................................................................... 681
11.3. Instrukcje manipulujące bitami............................................................................. 683
11.4. Znacznik przeniesienia w roli akumulatora bitów ................................................ 692
11.5. Wstawianie i wyodrębnianie łańcuchów bitów .................................................... 693
11.6. Scalanie zbiorów bitów i rozpraszanie łańcuchów bitowych................................ 696
11.7. Spakowane tablice łańcuchów bitowych .............................................................. 699
11.8. Wyszukiwanie bitów ............................................................................................ 701
11.9. Zliczanie bitów ..................................................................................................... 704

11.10. Odwracanie łańcucha bitów.................................................................................. 707
11.11. Scalanie łańcuchów bitowych .............................................................................. 709
11.12. Wyodrębnianie łańcuchów bitów ......................................................................... 710
11.13. Wyszukiwanie wzorca bitowego .......................................................................... 712
11.14. Moduł bits biblioteki standardowej HLA ............................................................. 713
11.15. Źródła informacji dodatkowych ........................................................................... 715

background image

10

Asembler. Sztuka programowania

Rozdział 12. Operacje łańcuchowe ..................................................................... 717

12.1. Wprowadzenie...................................................................................................... 717
12.2. Instrukcje łańcuchowe procesorów 80x86............................................................ 717

12.2.1. Sposób działania instrukcji łańcuchowych ................................................. 718
12.2.2. Przedrostki instrukcji łańcuchowych — repX ............................................ 719
12.2.3. Znacznik kierunku ...................................................................................... 719
12.2.4. Instrukcja movs........................................................................................... 721
12.2.5. Instrukcja cmps........................................................................................... 727
12.2.6. Instrukcja scas............................................................................................. 731
12.2.7. Instrukcja stos ............................................................................................. 732
12.2.8. Instrukcja lods ............................................................................................ 733
12.2.9. Instrukcje lods i stos w złożonych operacjach łańcuchowych .................... 733

12.3. Wydajność instrukcji łańcuchowych procesorów 80x86 ...................................... 734
12.4. Źródła informacji dodatkowych............................................................................ 735

Rozdział 13. Instrukcje MMX............................................................................. 737

13.1. Wprowadzenie...................................................................................................... 737
13.2. Sprawdzanie obecności rozszerzenia MMX ......................................................... 738
13.3. Środowisko programowania MMX ...................................................................... 739

13.3.1. Rejestry MMX............................................................................................ 739
13.3.2. Typy danych MMX .................................................................................... 741

13.4. Przeznaczenie instrukcji zestawu MMX............................................................... 742
13.5. Arytmetyka z nasycaniem a arytmetyka z zawijaniem ......................................... 743
13.6. Operandy instrukcji MMX ................................................................................... 744
13.7. Instrukcje zestawu MMX ..................................................................................... 746

13.7.1. Instrukcje transferu danych......................................................................... 747
13.7.2. Instrukcje konwersji.................................................................................... 747
13.7.3. Arytmetyka operandów spakowanych ........................................................ 752
13.7.4. Instrukcje logiczne...................................................................................... 755
13.7.5. Instrukcje porównań ................................................................................... 756
13.7.6. Instrukcje przesunięć bitowych................................................................... 760
13.7.7. Instrukcja emms.......................................................................................... 762

13.8. Model programowania MMX............................................................................... 763
13.9. Źródła informacji dodatkowych............................................................................ 774

Rozdział 14. Klasy i obiekty............................................................................... 775

14.1. Wprowadzenie...................................................................................................... 775
14.2. Wstęp do programowania obiektowego................................................................ 775
14.3. Klasy w języku HLA ............................................................................................ 779
14.4. Obiekty ................................................................................................................. 782
14.5. Dziedziczenie ....................................................................................................... 784
14.6. Przesłanianie......................................................................................................... 785
14.7. Metody wirtualne a procedury statyczne .............................................................. 786
14.8. Implementacje metod i procedur klas ................................................................... 788
14.9. Implementacja obiektu ......................................................................................... 793

14.9.1. Tabela metod wirtualnych .......................................................................... 796
14.9.2. Reprezentacja w pamięci obiektu klasy pochodnej..................................... 798

14.10. Konstruktory i inicjalizacja obiektów................................................................... 802

14.10.1. Konstruktor a dynamiczny przydział obiektu ............................................. 804
14.10.2. Konstruktory a dziedziczenie...................................................................... 806
14.10.3. Parametry konstruktorów i przeciążanie procedur klas .............................. 810

14.11. Destruktory........................................................................................................... 811
14.12. Łańcuchy _initialize_ oraz _finalize_ w języku HLA .......................................... 812
14.13. Metody abstrakcyjne ............................................................................................ 818

background image

Spis treści

11

14.14. Informacja o typie czasu wykonania (RTTI) ........................................................ 822
14.15. Wywołania metod klasy bazowej ......................................................................... 824
14.16. Źródła informacji dodatkowych ........................................................................... 825

Rozdział 15. Na styku asemblera i innych języków programowania...................... 827

15.1. Wprowadzenie...................................................................................................... 827
15.2. Łączenie kodu HLA i kodu asemblera MASM bądź Gas ..................................... 827

15.2.1. Kod asemblera MASM (Gas) rozwijany w kodzie języka HLA ................. 828
15.2.2. Konsolidacja modułów MASM (Gas) z modułami HLA............................ 832

15.3. Moduły HLA a programy języka Delphi (Kylix) ................................................. 837

15.3.1. Konsolidacja modułów HLA z programami języka Delphi (Kylix) ........... 838
15.3.2. Zachowywanie wartości rejestrów.............................................................. 842
15.3.3. Wartości zwracane funkcji.......................................................................... 843
15.3.4. Konwencje wywołań .................................................................................. 849
15.3.5. Przekazywanie argumentów przez wartość i adres,

parametry niemodyfikowalne i wyjściowe ................................................. 854

15.3.6. Skalarne typy danych w językach HLA i Delphi (Kylix) ........................... 856
15.3.7. Przekazywanie łańcuchów znaków z Delphi do procedury HLA ............... 858
15.3.8. Przekazywanie rekordów z programu w języku Delphi do kodu HLA....... 862
15.3.9. Przekazywanie zbiorów z programu w języku Delphi do kodu HLA ......... 866

15.3.10. Przekazywanie tablic z programu w języku Delphi do kodu HLA ............. 867
15.3.11. Odwołania do obiektów programu pisanego w Delphi w kodzie HLA....... 867

15.4. Moduły HLA a programy języków C i C++ ......................................................... 870

15.4.1. Konsolidacja modułów języka HLA z programami języków C i C++........ 872
15.4.2. Zachowywanie wartości rejestrów.............................................................. 875
15.4.3. Wartości funkcji ......................................................................................... 876
15.4.4. Konwencje wywołań .................................................................................. 876
15.4.5. Tryby przekazywania argumentów ............................................................. 880
15.4.6. Odwzorowanie typów skalarnych

pomiędzy językiem C (C++) a językiem HLA ........................................... 881

15.4.7. Przekazywanie łańcuchów znaków

pomiędzy programem w języku C (C++) a modułem HLA........................ 883

15.4.8. Przekazywanie rekordów

pomiędzy programem w języku C (C++) a modułem HLA........................ 883

15.4.9. Przekazywanie tablic

pomiędzy programem w języku C (C++) a modułem HLA........................ 886

15.5. Źródła informacji dodatkowych............................................................................ 886

Dodatek A  Tabela kodów ASCII....................................................................... 887

Dodatek B  Instrukcje procesorów 80x86......................................................... 891

Skorowidz...................................................................................... 927

background image

1.9. Podstawowe struktury sterujące
wykonaniem programu HLA

Instrukcje 

 oraz 

, choć niezwykle użyteczne, nie wystarczają do napisania

sensownych programów. Aby takie programy mogły powstać, podstawowe instrukcje
muszą zostać uzupełnione o podstawowy zbiór struktur sterujących wykonaniem kodu
umożliwiających  tworzenie  pętli  i  podejmowanie  decyzji  w  programie.  Język  HLA
przewiduje  szereg  wysokopoziomowych  struktur  sterujących  wykonaniem  charaktery-
stycznych dla języków wyższego niż asembler poziomu. Programista HLA ma więc do
dyspozycji  konstrukcje 

 i tak dalej. Dopiero znajomość i umiejętność  wykorzystywania owych konstrukcji

pozwala na stworzenie prawdziwego programu.

Przed zagłębieniem się w cechach struktur sterujących należałoby podkreślić, że nie mają
one odpowiedników w zestawie instrukcji maszynowych procesorów 80x86. Kompi-
lator HLA tłumaczy owe struktury do postaci szeregu prawdziwych instrukcji maszyno-
wych za programistę. Sposób, w jaki to czyni, opisany zostanie w dalszej części książki;
wtedy też Czytelnik nauczy się pisać kod w czystym języku asemblerowym, bez wy-
korzystania  wysokopoziomowych  cech  kompilatora  HLA.  Na  razie  jednak,  z  racji
niewielkiego jeszcze zasobu wiedzy o języku asemblerowym, nieco sztuczne struktury
sterujące HLA będą bardzo pomocne.

Warto przy tym pamiętać, że wysokopoziomowe na pierwszy rzut oka struktury sterujące
nie są w istocie tak wysokopoziomowe. Mają one bowiem jedynie ułatwić rozpoczęcie
pisania programów w języku asemblerowym — w żadnym razie nie zastępują asem-
blerowych instrukcji sterujących wykonaniem kodu. Już wkrótce okaże się więc, że owe
wysokopoziomowe struktury sterujące  mają szereg ograniczeń, które  w  miarę  nabie-
rania przez Czytelnika doświadczenia i  w miarę komplikowania kolejnych programów
staną się dość uciążliwe. To zresztą efekt zamierzony — po osiągnięciu pewnego poziomu
umiejętności programista sam stwierdzi, że w wielu przypadkach wygoda (i czytelność
programu)  wynikająca  z  zastosowania  struktur  sterujących  HLA  nie  rekompensuje
utraty efektywności, charakterystycznej dla kodu wykorzystującego wprost instrukcje
maszynowe procesora.

Niniejsze omówienie kierowane jest do tych Czytelników, którzy znają już przynajmniej
jeden z popularnych języków wysokiego poziomu. Znajomość ta znakomicie uprości
prezentację struktur sterujących wykonaniem programu HLA, pozwalając na pominięcie
opisów sposobów zastosowania owych struktur w typowych zadaniach programistycz-
nych. Czytelnicy potrafiący korzystać ze struktur sterujących  wykonaniem programu
w dowolnym z języków wysokiego poziomu nie powinni być zaskoczeni, kiedy odkryją,
że analogiczne struktury wykorzystywane są w programach HLA w sposób dosłownie
identyczny.

background image

Rozdział 1. 

♦ Wstęp do języka asemblerowego

31

1.9.1. Wyrażenia logiczne w instrukcjach HLA

Niektóre  z  instrukcji  sterujących  wykonaniem  programu  HLA  wymagają  określenia
w miejsce operandu wartości logicznej („prawda” bądź „fałsz”); ścieżka wykonania pro-
gramu zależy  wtedy od  wartości  wyrażenia logicznego.  Przykładami  struktur  wyma-
gających określenia wyrażenia logicznego są instrukcje 

 oraz 

.

Pierwszą  oznaką  ograniczeń  struktur  sterujących  wykonaniem  programu  HLA  jest
składnia owych wyrażeń. W tym jednym miejscu nawyki wyniesione z języków wyso-
kiego poziomu zwracają się przeciwko programiście asemblera: w języku HLA wyra-
żenia logiczne są bardzo ograniczone — nie sposób konstruować tu  wyrażeń równie
wymyślnych, z jakimi może mieć do czynienia programista języka C++.

Wyrażenia logiczne w języku HLA mogą przyjąć jedną z następujących postaci

1

:

Znacznik  może zostać określony za pośrednictwem jednego  z  symboli  wyliczonych
w tabeli 1.2.

Tabela 1.2. Symbole reprezentujące znaczniki

Symbol

Znacznik

Znaczenie

Przeniesienie

„Prawda” dla ustawionego (1) znacznika przeniesienia;
„fałsz” dla wyzerowanego (0) znacznika przeniesienia.

Brak przeniesienia

„Prawda” dla wyzerowanego (0) znacznika przeniesienia;
„fałsz” dla ustawionego (1) znacznika przeniesienia.

Zero

„Prawda” dla ustawionego (1) znacznika zera; „fałsz”
dla wyzerowanego (0) znacznika zera.

Brak zera

„Prawda” dla wyzerowanego (0) znacznika zera; „fałsz”
dla ustawionego (1) znacznika zera.

Przepełnienie

„Prawda” dla ustawionego (1) znacznika przepełnienia;
„fałsz” dla wyzerowanego (0) znacznika przepełnienia.

Brak przepełnienia

„Prawda” dla wyzerowanego (0) znacznika przepełnienia;
„fałsz” dla ustawionego (1) znacznika przepełnienia.

Znak

„Prawda” dla ustawionego (1) znacznika znaku; „fałsz”
dla wyzerowanego (0) znacznika znaku.

Brak znaku

„Prawda” dla wyzerowanego (0) znacznika znaku; „fałsz”
dla ustawionego (1) znacznika znaku.

                                                          

1

Istnieje jeszcze kilka postaci dodatkowych; zostaną one omówione w dalszych podrozdziałach
i rozdziałach.

background image

32

Asembler. Sztuka programowania

Wykorzystanie tych znaczników w wyrażeniach logicznych to już dość zaawansowane
programowanie w asemblerze. Sposób konstruowania wyrażeń logicznych angażujących
znaczniki słowa stanu procesora zaprezentowany zostanie w następnym rozdziale.

W przypadku, kiedy wartość logiczna angażuje operand rejestrowy, operand ten może
odnosić się do zarówno do rejestru 8-bitowego, 16-bitowego, jak i 32-bitowego. Wyra-
żenie takie przyjmuje wartość „fałsz”, jeśli rejestr zawiera wartość zero. Dla każdej innej
wartości rejestru wyrażenie logiczne przyjmuje wartość „prawda”.

Jeśli w wyrażeniu logicznym uwikłana jest zmienna przechowywana w pamięci, jej war-
tość jest sprawdzana, a wyrażenie przyjmuje wartość logiczną „prawdy” bądź „fałszu”
w  zależności  od  tej  wartości,  według  reguł  identycznych  jak  dla  rejestrów.  Należy
jednak pamiętać, że w wyrażeniu logicznym powinna występować zmienna typu logicz-
nego (

). Uwikłanie w wyrażeniu logicznym zmiennej innego typu spowoduje

błąd. W przypadku potrzeby określenia  wartości  logicznej  zmiennej  typu  innego  niż

  należy  skorzystać  z  wyrażeń  logicznych  ogólnego  przeznaczenia,  omówio-

nych poniżej.

Najbardziej ogólna postać wyrażenia logicznego w języku HLA pozwala na określenie
operatora relacji wraz z dwoma operandami. Listę dozwolonych kombinacji rodzajów
operandów i operatorów relacji zawiera tabela 1.3.

Tabela 1.3. Dozwolone wyrażenia logiczne

Operand lewy

Operator relacji

Operand prawy

Zmienna w pamięci bądź rejestr

 albo 

Zmienna w pamięci, rejestr bądź stała

 albo 




Nie można określić obu operandów jako operandów pamięciowych. Lewy operand moż-
na więc utożsamić z operandem źródłowym, a operand prawy z operandem docelowym
i stosować kombinacje rodzajów operandów takie jak dla instrukcji 

 czy 

.

Kolejna  analogia  operatorów  relacji  do  instrukcji 

  i 

  objawia  się  w  wymogu

identycznego rozmiaru operandów. I tak, oba operandy muszą być albo bajtami, albo
słowami, albo podwójnymi słowami. Jeśli operand prawy jest stałą, jego wartość musi
nadawać się do zapisania w operandzie lewym.

I jeszcze jedno: jeśli lewy operand jest rejestrem, a prawy to stała dodatnia albo inny
rejestr, porównanie odbywa się bez uwzględnienia znaku operandów. Efekty takiego
porównania omówione zostaną w następnym rozdziale; na razie wystarczy zapamiętać,
że nie powinno się porównywać wartości ujemnej przechowywanej w rejestrze z warto-
ścią stałą albo zawartością innego rejestru. Wynik porównania może bowiem odbiegać
od oczekiwań programisty.

background image

Rozdział 1. 

♦ Wstęp do języka asemblerowego

33

Operatory 

  i 

 

  pozwalają  na  wykonanie  testu  zawierania  się  wartości  prze-

chowywanej w rejestrze w określonym zbiorze liczb. Na przykład  wyrażenie 

  daje  wartość  logiczną 

,  jeśli  wartość  przechowywana  w  rejestrze

EAX to wartość z zakresu od 2000 do 2099 (włącznie). Operator 

 ma działanie

odwrotne  —  da  wartość  logiczną 

  jedynie  wtedy,  kiedy  zawartość  rejestru  to

liczba spoza określonego zbioru. Na przykład wyrażenie 

 będzie

miało wartość 

 tylko wtedy, kiedy znak przechowywany w rejestrze AL nie będzie

małą literą alfabetu.

Oto kilka przykładów wyrażeń logicznych w języku HLA:

 !""

#$$$$

1.9.2. Instrukcje if..then..elseif..else..endif języka HLA

Składnia instrukcji warunkowego  wykonania kodu 

 w wydaniu języka HLA prezen-

towana jest na rysunku 1.10.

Rysunek 1.10. Składnia instrukcji if w języku HLA

Wyrażenia  występujące  w  instrukcji 

  muszą odpowiadać jednej  z  postaci  wyrażeń  lo-

gicznych,  zaprezentowanych  w  poprzednim  ustępie.  Jeśli  wyrażenie  przyjmie  wartość  lo-
giczną  „prawda”,  wykonany zostanie kod umieszczony za klauzulą 

. W przeciwnym

przypadku sterowanie przekazane zostanie do kodu w ramach klauzuli 

 bądź 

.

background image

34

Asembler. Sztuka programowania

Ponieważ  klauzule 

  i 

  nie  są  obowiązkowe,  instrukcja 

  w  najprostszej

postaci składa się z  wyrażenia logicznego i pojedynczej klauzuli 

, za którą  wystę-

puje instrukcja lub blok instrukcji wykonywanych warunkowo, w zależności od war-
tości wyrażenia logicznego:

%& !"'# 

()*)&+,(-./011+2'3

 (%3

Jeśli w trakcie wykonania programu wyrażenie logiczne w instrukcji 

 będzie miało

wartość 

, wykonany zostanie kod znajdujący się pomiędzy słowami 

 i 

.

W przypadku kiedy wyrażenie logiczne będzie miało wartość 

, kod ów zostanie

pominięty.

Inną  powszechnie  występującą  formą  instrukcji 

 jest  forma  z  pojedynczą  klauzulą

. Instrukcja taka wygląda następująco:

%& !"'# 

()*)&+,(-./011.44+2'3

  

()*)&+5*...44+2'3

 (%3

Jeśli wyrażenie logiczne przyjmie wartość 

, wykonany zostanie kod zawarty między

słowami 

 a 

. W przypadku wartości 

 sterowanie przekazane zostanie do

kodu ograniczonego klauzulą 

 i słowem 

.

Przez  osadzanie  opcjonalnych  klauzul 

  w  instrukcji 

  można  rozbudowywać

podejmowanie decyzji o wykonaniu kodu. Na przykład, jeśli rejestr CH zawiera wartość
znakową,  można  na  podstawie  jego  wartości  dokonać  wyboru  spośród  dostępnych
opcji menu, konstruując następującą instrukcję 

:

%&#$$'#

()*)&+67,*78 9 )-$$+2'3

  %&#$,$'#

()*)&+67,*78 9 )-$,$+2'3

  %&#$$'

()*)&+67,*78 9 )-$$+2'3

  

()*)&+(-.7,*8*9 )+2'3

 (%3

background image

Rozdział 1. 

♦ Wstęp do języka asemblerowego

35

Choć nie widać tego w powyższym przykładzie, język HLA nie wymaga umieszczania
po sekwencji klauzul 

 klauzuli 

. Jednak przy podejmowaniu decyzji o wyko-

naniu jednej z wielu ścieżek kodu warto uzupełnić instrukcję 

 klauzulą 

 rozpo-

czynającą  ścieżkę  kodu  obsługi  ewentualnych  błędów  wyboru.  Nawet  jeśli  pozornie
wykonanie owego kodu wydaje się niemożliwe, to należy wziąć pod uwagę ewentualny
rozwój aplikacji — w kolejnych wersjach kodu wybór ścieżki wykonania może mieć
inny przebieg; warto, aby wtedy kod decyzyjny uzupełniony został o obsługę sytuacji
wyjątkowych i niespodziewanych.

1.9.3. Iloczyn, suma i negacja
w wyrażeniach logicznych

Na liście operatorów zaprezentowanej w poprzednich punktach zabrakło bardzo ważnych
operatorów  logicznych:  operatora  iloczynu  logicznego  (AND),  logicznej  sumy  (OR)
oraz logicznej negacji (NOT). Ich zastosowanie w wyrażeniach logicznych omówione
zostanie tutaj — wcześniej omówienie takie nie miałoby racji bytu z uwagi na koniecz-
ność poprzedzenia go omówieniem instrukcji warunkowego wykonania kodu 

. Bez

możliwości  wybiórczego  wykonywania  kodu  trudno  zaprezentować  realistyczne  przy-
kłady zastosowania operatorów logicznych.

W języku HLA operator iloczynu  logicznego  ma  postać  znaków 

.  Jest  to  operator

binarny i jako taki wymaga określenia dwóch operandów; operandy te muszą być po-
prawnymi wyrażeniami czasu wykonania. Operator ten zwraca wartość logiczną „prawda”
wtedy, kiedy oba operandy mają wartość „prawda”. Oto przykład:

%& !"::#$$'#

9& !2 ,!'3

9&$$2#'3

 (%3

Obie instrukcje 

 zostaną wykonane jedynie wtedy, kiedy równocześnie rejestr EAX

będzie miał wartość większą od 0, a rejestr CH będzie zawierał znak „a”. Jeśli który-
kolwiek z tych warunków nie będzie spełniony, obie instrukcje 

 zostaną pominięte.

Warto pamiętać, że operandy operatora 

 mogą być dowolnymi poprawnymi  wyra-

żeniami logicznymi języka HLA — nie muszą to być wyłącznie operatory relacji. Oto
kilka poprawnych logicznych z operatorem iloczynu logicznego:

::"

$$$$:: ,!

,;:: !

W języku HLA wykorzystywana jest metoda skróconego szacowania wartości wyrażeń
operandów  operatora  logicznego 

.  Otóż  jeśli  lewy  operand  ma  wartość  „fałsz”,  to

operand prawy nie jest już sprawdzany — wyrażenie logiczne natychmiast otrzymuje
wartość „fałsz” (co jest jak najbardziej poprawne, bo operator 

 daje wartość „prawda”

wyłącznie dla dwu operandów o wartości „prawda”). Stąd w ostatnim z zaprezentowanych
wyżej wyrażeń najpierw sprawdzona zostanie wartość logiczna zmiennej 

;  jeśli

będzie to wartość 

, test wartości logicznej rejestru EAX zostanie pominięty.

background image

36

Asembler. Sztuka programowania

Nie sposób nie zauważyć, że wyrażenie z operatorem 

, np. 

,

jest  wyrażeniem  logicznym  (zwraca  albo  wartość  logiczną  „prawda”,  albo  wartość
„fałsz”),  więc  wyrażenie  takie  może  występować  w  roli  operandu  innego  wyrażenia
z operatorem logicznym. Stąd poprawność następującego wyrażenia:

 !":: ,! !:: !

Operator iloczynu logicznego cechuje się łącznością lewostronną, co oznacza, że w kodzie
wygenerowanym przez kompilator HLA ewaluacja (wartościowanie) wyrażenia następuje
od strony lewej do prawej. Jeśli rejestr EAX zawiera wartość większą od zera, pozostałe
operandy nie będą sprawdzane. Analogicznie, jeśli EAX jest mniejszy od zera, ale EBX
jest równy EAX, to sprawdzanie logicznej wartości wyrażenia 

 !

 zostanie pominięte —

całe wyrażenie od razu otrzyma wartość 

.

Operator sumy logicznej w języku HLA reprezentowany jest znakami 

""

. Podobnie, jak

operator iloczynu, operator sumy logicznej wymaga określenia dwóch operandów będą-
cych poprawnymi wyrażeniami logicznymi. Operator zwraca przy tym wartość logiczną
„prawda”,  jeśli  którykolwiek  z  operandów  ma  wartość  logiczną  „prawda”.  Również
podobnie jak w przypadku operatora iloczynu stosowana jest metoda skróconej ewalu-
acji wyrażenia — jeśli już pierwszy operand ma wartość logiczną „prawda”, operator
natychmiast zwraca wartość „prawda”, niezależnie od wartości drugiego operandu. Oto
przykłady zastosowania operatora sumy logicznej:

<<"

$$$$<< ,!

,;<< !

Podobnie jak operator 

 operator logicznej sumy cechuje się łącznością lewostronną,

więc w wyrażeniach z wieloma operatorami 

""

 ewaluacja przebiega od strony lewej do

prawej. Na przykład:

 !"<< ,! !<< !

Jeśli powyższe wyrażenie zostałoby osadzone w wyrażeniu logicznym instrukcji 

, to

kod po klauzuli 

 zostałby wykonany tylko wtedy, kiedy rejestr EAX miałby wartość

mniejszą od zera lub EBX nie byłby równy EAX bądź rejestr ECX zawierałby wartość
zero. Jeśli już pierwszy test dałby wartość 

, pozostałe warunki nie byłyby spraw-

dzane. Gdyby pierwszy warunek miał wartość 

, ale drugi 

, to etap szacowania

wartości logicznej  wyrażenia 

 !

 zostałby pominięty. Test zawartości rejestru ECX

nastąpiłby jedynie w obliczu niespełnienie wcześniejszych dwóch wyrażeń.

Jeśli w jednym wyrażeniu logicznym występują operatory iloczynu i sumy logicznej,
pierwszeństwo przed operatorem sumy ma operator iloczynu. Weźmy następujące wy-
rażenie:

 !"<< ,! !:: !

W interpretacji HLA powyższemu wyrażeniu równoważne jest wyrażenie następujące:

 !"<<& ,! !:: !'

Jeśli rejestr EAX będzie miał wartość  mniejszą od zera, reszta wyrażenia nie  będzie
sprawdzana — całość wyrażenia otrzyma wartość 

. Jeśli jednak rejestr EAX będzie

background image

Rozdział 1. 

♦ Wstęp do języka asemblerowego

37

zawierał  wartość  większą  od  zera,  to  aby  całość  wyrażenia  otrzymała  wartość 

,

oba operandy operatora iloczynu musiałyby mieć wartość 

.

Podwyrażenia wykorzystujące operatory iloczynu i sumy logicznej  można grupować
wedle własnego uznania, otaczając je nawiasami. Oto przykładowe wyrażenie:

& !"<< ,! !':: !

Aby  to  wyrażenie otrzymało wartość logiczną  „prawda”, rejestr ECX  musi  zawierać
wartość zero oraz albo wartość EAX musi być mniejsza od zera, albo rejestr EBX musi
zawierać  wartość różną od rejestru  EAX.  Szacowanie  wyrażenia  przebiega  więc  od-
miennie niż w wersji bez nawiasów.

Operator negacji logicznej ma w języku HLA postać znaku wykrzyknika (

 

). Operan-

dami operatora negacji mogą być jednak wyłącznie rejestry i zmienne w pamięci (np.

 

) — operator negacji nie  może być bezpośrednio aplikowany do złożonych

wyrażeń logicznych. Aby wykonać logiczną negację wyrażenia logicznego, należy to
wyrażenie ująć w nawiasy i cały nawias opatrzyć przedrostkiem w postaci wykrzyknika:

& !"'

Powyższe  wyrażenie  otrzymuje  wartość 

  wtedy,  kiedy  rejestr  EAX  ma  wartość

mniejszą od zera.

Operator logicznej negacji jest szczególnie użyteczny w odniesieniu do wyrażeń zło-
żonych angażujących operatory logicznej sumy i logicznego iloczynu. W prostych wyra-
żeniach, jak powyższe, lepiej jest zazwyczaj bezpośrednio określić stan logiczny wyra-
żenia niż dodatkowo komplikować go operatorem negacji.

W języku HLA programista może też korzystać z operatorów bitowych 

"

 i 

, ale te różnią

się znacząco od operatorów logicznych 

""

 oraz 

. Szczegółowe omówienie operatorów

bitowych i innych operatorów języka HLA znajduje się w dokumentacji kompilatora.

1.9.4. Instrukcja while

Składnię instrukcji 

 ilustruje rysunek 1.11.

Rysunek 1.11.
Składnia instrukcji
while języka HLA

Wykonanie instrukcji 

  przebiega  następująco:  najpierw  szacowana  jest  wartość

logiczna  wyrażenia  warunku  pętli.  Jeśli  ma  ono  wartość  „fałsz”,  sterowanie  natych-
miast przekazywane jest do kodu znajdującego się za klauzulą 

. W przypadku

background image

38

Asembler. Sztuka programowania

wartości „prawda” wyrażenia warunku, procesor przystępuje do wykonania ciała pętli.
Po wykonaniu wszystkich instrukcji ciała pętli następuje ponowne oszacowanie wartości
logicznej  wyrażenia  warunkowego.  Cała  procedura  powtarzana  jest  aż  do  momentu,
w którym wyrażenie warunkowe pętli przyjmie wartość logiczną „fałsz”.

W pętli 

 sprawdzenie wartości warunku wykonania pętli następuje podobnie jak

w  językach  wysokiego  poziomu,  czyli  przed  wykonaniem  pierwszej  instrukcji  ciała
pętli. Z tego względu istnieje prawdopodobieństwo, że instrukcje ciała pętli nie zostaną
wykonane ani razu, jeśli wyrażenie warunku będzie od początku miało wartość 

.

Poza tym warto pamiętać, że przynajmniej jedna z instrukcji ciała pętli powinna mody-
fikować  wartość  wyrażenia logicznego —  w przeciwnym przypadku będziemy  mieli
do czynienia z pętlą nieskończoną.

Oto przykład pętli 

 w języku HLA:

9&"2'3

.# &"'(

()*)&++22'3

((&2'3

 (.# 3

1.9.5. Instrukcja for

W języku HLA pętla for przybiera następującą postać:

%&33'(

 (%3

Instrukcja pętli 

 odpowiada następującej pętli 

:

3

.# &'(

3

 (.# 3

Wyrażenie inicjalizujące może być dowolną, pojedynczą instrukcją maszynową. Instruk-
cja ta zwykle inicjalizuje rejestr albo zmienną przechowywaną w pamięci, której wartość
decyduje o kontynuowaniu pętli (tzw. licznik pętli). Warunek to wyrażenie logiczne
języka HLA w formacie takim jak dla instrukcji 

. Wartość tego wyrażenia decy-

duje o podjęciu kolejnej iteracji pętli i zwykle angażuje licznik pętli. Instrukcja licz-
nika  to  typowo  instrukcja  zwiększająca  licznik  pętli;  odpowiednikiem  tej  instrukcji
jest  instrukcja  w  pętli 

,  której  wykonanie  zmienia  wartość  logiczną  warunku

wykonania pętli. Zwykle instrukcją tą jest instrukcja języka HLA, na przykład 

.

background image

Rozdział 1. 

♦ Wstęp do języka asemblerowego

39

Oto przykład ilustrujący równoważność pętli 

 i 

:

%&9&"2'3"3((&2''(

()*)&++22'3

 (%3

// Analogiczna pętla for:

9&"2'3

.# &"'(

()*)&++22'3

((&2'3

 (.# 3

1.9.6. Instrukcja repeat

Instrukcja 

 języka HLA wykorzystuje składnię prezentowaną na rysunku

1.12. Programiści języków C, C++ oraz Java powinni od razu zauważyć, że instrukcja
ta jest odpowiednikiem dostępnej w tych językach instrukcji 

.

Rysunek 1.12.
Składnia instrukcji
repeat..until
języka HLA

Test  warunku zatrzymania pętli 

 odbywa się  po  zakończeniu  wykony-

wania ciała pętli. Z tego względu instrukcja (bądź blok instrukcji) ciała pętli zostanie
wykonana przynajmniej raz, niezależnie od wartości początkowej wyrażenia warunku
zatrzymania pętli. Warunek ten jest badany po raz pierwszy dopiero po zakończeniu
pierwszej iteracji; jeśli wyrażenie ma wartość 

, pętla jest powtarzana

2

. Jeśli wa-

runek otrzyma wartość 

, pętla jest zatrzymywana i sterowanie przekazywane jest

do instrukcji znajdujących się za klauzulą 

.

Oto prosty przykład wykorzystania pętli 

:

9&"2 !'3

 * 

()*)&+ !+2 !2'3

),&2 !'3

)& !"'3

                                                          

2

Znaczenie warunku jest więc w języku HLA odwrotne niż warunku w analogicznej konstrukcji

(.# 

 znanej z języków C, C++ i Java.

background image

40

Asembler. Sztuka programowania

Jeśli instrukcje ciała pętli mają być wykonane przynajmniej raz, niezależnie od warto-
ści wyrażenia warunku pętli, wtedy w miejsce pętli 

 lepiej zastosować pętlę 

#

 — konstrukcja taka będzie efektywniejsza.

1.9.7. Instrukcje break oraz breakif

Instrukcje 

$

 i 

$

 służą do przedwczesnego przekazywania  sterowania  poza

pętlę. Składnię obu instrukcji ilustruje rysunek 1.13.

Rysunek 1.13.
Składnia instrukcji
break oraz breakif
języka HLA

Instrukcja 

$

 powoduje bezwarunkowe przerwanie  wykonywania pętli; instrukcja

$

 uzależnia przekazanie sterowania poza pętlę od spełnienia warunku będącego

wyrażeniem logicznym. Wyjście z pętli następuje wtedy wyłącznie w obliczu wartości

 tego wyrażenia.

Zastosowanie instrukcji 

$

 oraz 

$

 nie pozwala na przekazanie sterowania poza

pętle zagnieżdżone. W języku HLA  służą  do  tego  osobne  instrukcje, jak 

%

oraz 

 i 

. Szczegółowe informacje na ten temat zawiera dokumentacja języka

HLA. Dalej, język HLA udostępnia instrukcje 

!

 oraz 

!

 pozwalające

na przystąpienie do kolejnej iteracji pętli z pominięciem niewykonanych jeszcze instrukcji
ciała pętli. Po szczegóły odsyłam Czytelników do stosownej dokumentacji.

1.9.8. Instrukcja forever

Składnię konstrukcji 

 prezentuje rysunek 1.14.

Rysunek 1.14.
Składnia instrukcji
forever języka HLA

Instrukcja 

  tworzy  pętlę  nieskończoną.  Wewnątrz  takiej  pętli  można  jednak

umieścić instrukcje 

$

 bądź 

$

 celem przerwania pętli w wybranym momencie.

To chyba najczęstszy układ pętli 

. Ilustruje go następujący przykład:

%  

()*)&+6*.(, 4.&9 8("'-+'3

(= &'3

, 4%&"'3

()*)&+6*.(.9,79 8("+2'3

 (%3

background image

Rozdział 1. 

♦ Wstęp do języka asemblerowego

41

1.9.9. Instrukcje try, exception oraz endtry

Instrukcje 

&!&

 służą do implementacji bardzo przydatnych bloków

obsługi wyjątków. Składnia instrukcji prezentowana jest na rysunku 1.15.

Rysunek 1.15.
Składnia instrukcji
obsługi wyjątków

Blok instrukcji 

&&

 służy do ochrony  wykonania instrukcji. Jeśli  wykonanie

instrukcji  znajdujących  się  pomiędzy  klauzulą 

&

  a  pierwszą  klauzulą 

!

,

tworzących tzw. blok chroniony, przebiegnie bez niespodzianek, sterowanie przeka-
zywane  jest  do  pierwszej  instrukcji  za  klauzulą 

&

.  Jeśli  jednak  w  czasie  wyko-

nywania instrukcji bloku chronionego zdarzy się błąd (wyjątek), wykonanie programu
jest przerywane, a sterowanie przekazywane jest do odpowiedniej klauzuli 

!

(skojarzonej z wyjątkiem,  który  wystąpił). Każdy  wyjątek  ma  unikalny identyfikator
w postaci liczby całkowitej. Niektóre z identyfikatorów wyjątków zadeklarowane zostały
w pliku nagłówkowym 

excepts.hhf biblioteki standardowej języka HLA, ale programi-

sta  może  też  definiować  własne  wyjątki.  W  momencie  sprowokowania  wyjątku  system
porównuje identyfikator zgłoszonego wyjątku z identyfikatorami określonymi  w  klauzu-
lach 

!

. Jeśli wśród nich znajduje odpowiedni identyfikator, następuje wykonanie

instrukcji określonych w ramach klauzuli 

!

. Po zakończeniu wykonywania tych

instrukcji sterowanie przekazywane jest do pierwszej instrukcji za klauzulą 

&

.

Jeśli wyjątek zostanie sprowokowany poza blokiem kodu chronionego (tzn. poza ramami
instrukcji 

&&

)  albo jeśli  w  ramach  instrukcji 

&&

  brak  jest  klauzuli

skojarzonej ze zgłoszonym wyjątkiem, program jest awaryjnie zatrzymywany.  Zatrzy-
maniu towarzyszy wyprowadzenie stosownego komunikatu na wyjście programu.

Instrukcje 

&&

 można  na  przykład  wykorzystać  do  ochrony  programu  przed

skutkami wprowadzenia niepoprawnych danych:

background image

42

Asembler. Sztuka programowania

 * 

9&% 2>( = '3

// Uwaga: zmienna GoodInteger musi być typu boolean

7

()*)&+6*.(, 4.-+'3

(= &'3

9&) 2>( = '3

 ! *& !? '

()*)&+/ **..2.*.(8  +2'3

 ! *& !;) @)@%A= '

()*)&+6*4 )2.*.(8  +2'3

 (73

)&>( = '3

Zastosowana  w  powyższym  programie  pętla 

  pozwala  na  wykonywanie  kodu

ciała pętli tak długo, jak długo użytkownik podaje niepoprawne wartości. Jeśli z powodu
wprowadzenia niepoprawnej wartości jej przypisanie do zmiennej 

'(%

 będzie

niemożliwe (na przykład z racji umieszczenia we wprowadzonym niedozwolonych dla
liczb  znaków  albo  wprowadzenia  zbyt  wielkiej  wartości),  wykonany  zostanie  odpo-
wiedni do rodzaju błędu blok instrukcji obsługi wyjątku. Obsługa wyjątków polega tu
na  wyświetleniu  stosownego  komunikatu  i  przekazaniu  sterowania  poza  instrukcję

&&

. Ponieważ z racji wyjątku nie udało się w ramach bloku chronionego ustawić

wartości 

 dla zmiennej 

'(%

, pętla jest wykonywana ponownie. Jeśliby jedna

w ramach bloku chronionego sprowokowany został inny niż obsługiwane wyjątek, pro-
gram zakończyłby działanie po uprzednim wyprowadzeniu komunikatu o błędzie

3

.

W tabeli 1.4 zebrane zostały wyjątki, zdefiniowane w pliku 

excepts.hhf w czasie przy-

gotowywania wydania niniejszej książki. Aktualną listę zdefiniowanych  w bibliotece
standardowej wyjątków można znaleźć, zaglądając do pliku 

excepts.hhf dołączonego

do posiadanej wersji oprogramowania kompilatora HLA.

Większość z tych wyjątków dotyczy sytuacji, których omawianie w niniejszym rozdziale
byłoby  przedwczesne.  Zostały  umieszczone  w  tabeli  wyłącznie  gwoli  kompletności
omówienia. Każdy z wyjątków opisany jest szczegółowo w dokumentacji 

HLA Reference

Manual dokumentacji biblioteki standardowej języka HLA; ostateczną wykładnią jest
zaś sam kod źródłowy biblioteki standardowej HLA. Najczęściej wykorzystywanymi
wyjątkami są 

)*

++,%

 oraz 

-%+

.

Omówienie bloku kodu chronionego podjęte zostanie ponownie nieco później. Czytelnik
powinien kontynuować edukację w zakresie podstawowym, zadowalając się na razie
szczątkowymi informacjami o obsłudze wyjątków.

                                                          

3

Doświadczony programista zastanawia się zapewne, dlaczego w powyższym kodzie do sterowania
przebiegiem pętli wykorzystywana jest zmienna typu 

 — przecież można by identyczny efekt

osiągnąć, stosując instrukcję 

, 4%

. Zastosowana konstrukcja ma uzasadnienie, które zostanie

przedstawione w dalszej części książki.

background image

Rozdział 1. 

♦ Wstęp do języka asemblerowego

43

Tabela 1.4. Wyjątki zdefiniowane w pliku nagłówkowym excepts.hhf

Identyfikator wyjątku

Znaczenie

 !=@ %.

Próba zapisania zbyt długiego łańcucha

 !=( !

Próba odwołania do znaku o indeksie spoza łańcucha

 !;) @)@%A= 

Zbyt wielka wartość dla danego typu zmiennej

 ! =?#

Zaangażowanie w operacji znaku spoza dopuszczalnego zakresu
(kod ASCI spoza zakresu od 0 do 127)

 !? 

Niedozwolone znaki w konwersji łańcucha do wartości numerycznej

 !(B C(

Odwołanie do pliku za pośrednictwem niepoprawnego uchwytu pliku

 !B @* B) 

Niemożność otwarcia pliku (np. brak pliku o zadanej nazwie)

 !B ? 

Niemożność zamknięcia pliku

 !B 6 

Błąd przy zapisie danych do pliku

 !B A (

Błąd przy odczycie danych z pliku

 !D4B)

Próba zapisania danych do pliku przy pełnym dysku

 !(@%B 

Próba odczytania bajta spoza końca pliku

 !E 97B) 

Brak pamięci do zaspokojenia żądania alokacji

 ! 9*FD  %/011

Próba odwołania do pamięci za pośrednictwem wskaźnika o wartości NULL

 !?B  E 97

Błąd przy operacji zwalniania pamięci

 !6(#F=

Zły format konwersji wartości liczbowej do łańcucha

 !FE7?9(159

Wiersz wywołania programu zawiera zbyt dużą liczbę argumentów

 !7#* ;

Próba operowania na dwóch tablicach o różnych rozmiarach

 !7)(

Próba odwołania do elementu spoza tablicy

 !(D 

Operacja na niepoprawnej wartości daty

 !(D B9

Błąd konwersji łańcucha do daty (łańcuch zawiera niedozwolone znaki)

 !F9 @ %.

Przepełnienie w operacji manipulowania informacją o czasie

 ! B (

Błąd instrukcji asercji

 !! ) (,

Próba wywołania metody klasy abstrakcyjnej

 ! ;

Próba odwołania się do niedozwolonego adresu pamięci

 ! 45

Program dotarł do punktu zatrzymania (przerwanie 3.)

 !=  *

Program wykonywany jest z ustawionym znacznikiem wykonania
krokowego

 !5

Próba wykonania instrukcji charakterystycznej dla trybu nadzoru

 ! =

Próba wykonania niedozwolonej instrukcji maszynowe

 !)(

Rejestr zawiera wartość spoza zakresu określonego w instrukcji 

,)(

 !

Wykonanie instrukcji 

 przy ustawionym znaczniku przepełnienia

 !D(

Próba dzielenia przez zero bądź inny błąd dzielenia

 !%D 9

Wyjątek operacji zmiennoprzecinkowej (patrz rozdział poświęcony
obliczeniom arytmetycznym)

 !%D7G 

Wyjątek operacji zmiennoprzecinkowej (patrz rozdział poświęcony
obliczeniom arytmetycznym)

background image

44

Asembler. Sztuka programowania

Tabela 1.4. Wyjątki zdefiniowane w pliku nagłówkowym excepts.hhf — ciąg dalszy

Identyfikator wyjątku

Znaczenie

 !% !A )

Wyjątek operacji zmiennoprzecinkowej (patrz rozdział poświęcony
obliczeniom arytmetycznym)

 !%(@* 

Wyjątek operacji zmiennoprzecinkowej (patrz rozdział poświęcony
obliczeniom arytmetycznym)

 !%@ %.

Wyjątek operacji zmiennoprzecinkowej (patrz rozdział poświęcony
obliczeniom arytmetycznym)

 !%4?# 4

Wyjątek operacji zmiennoprzecinkowej (patrz rozdział poświęcony
obliczeniom arytmetycznym)

 !%0( %.

Wyjątek operacji zmiennoprzecinkowej (patrz rozdział poświęcony
obliczeniom arytmetycznym)

 !(C( 

System operacyjny odrzucił niepoprawny uchwyt

 !4@ %.

System operacyjny zgłosił przepełnienie stosu

1.10. Biblioteka standardowa
języka HLA — wprowadzenie

Język HLA prezentuje się na tle tradycyjnego języka asemblerowego korzystnie dzięki
dwóm aspektom. Pierwszym jest składnia języka HLA zapożyczająca elementy znane
z języków programowania wysokiego poziomu (jak struktury kontroli wykonania ko-
du czy deklaracje zmiennych). Dostępność tych elementów pozwala na efektywne wyko-
rzystanie  umiejętności  przeniesionych  z  programowania  wysokopoziomowego  i  tym
samym znacznie szybszą naukę języka asemblerowego. Drugim elementem stanowią-
cym o przewadze HLA nad tradycyjnym asemblerem jest dostępność biblioteki HLA
Standard Library. Biblioteka standardowa języka HLA zawiera wiele prostych w użyciu
procedur  języka  asemblerowego  realizujących  typowe  zadania  programistyczne  —  ich
dostępność eliminuje konieczność samodzielnego pisania wszystkich, nawet tych naj-
bardziej typowych, elementów programu. Dostępność biblioteki standardowej znosi jedną
z podstawowych trudności, w obliczu których stają programiści chcący poznać język
asemblerowy — trudność tkwiącą w konieczności samodzielnej implementacji złożonych
niekiedy procedur  wejścia-wyjścia,  niezbędnych  w  nawet  najprostszych  programach.
Brak standaryzowanej biblioteki wywołań powodowałby, że każdy początkujący progra-
mista  poświęcałby  mnóstwo  czasu  na  zaimplementowanie  tak  podstawowych  operacji
jak wyprowadzanie napisów. Jej dostępność oznacza więc, że adept języka asemblerowego
może skoncentrować się na nauce samego języka, nie poświęcając się od samego początku
zgłębianiu tajników podsystemu wejścia-wyjścia danego systemu operacyjnego.

Dostępność rozmaitych procedur bibliotecznych to nie jedyna zaleta języka HLA. W końcu
biblioteki  kodu  asemblerowego  to  nie  nowy  wynalazek

4

.  Jednak  biblioteka  standardowa

                                                          

4

Popularna jest na przykład biblioteka UCR Standard Library dla programistów języka asemblerowego
procesorów 80x86.

background image

Rozdział 1. 

♦ Wstęp do języka asemblerowego

45

języka HLA to nie tylko sam kod procedur, ale również wysokopoziomowy interfejs
wywołań tych procedur. W rzeczy samej język HLA powstał głównie z myślą o możli-
wości  tworzenia  zestawów  procedur  bibliotecznych  wysokiego  poziomu.  Ów  wyso-
kopoziomowy interfejs wywołań, w połączeniu z wysokopoziomową naturą wielu pro-
cedur,  daje  programiście  niespodziewanie  wielkie  i  równocześnie  łatwo  dostępne
możliwości.

Biblioteka standardowa języka HLA  składa  się  z  szeregu  modułów  przypisanych  do
kilku kategorii. Część z dostępnych modułów zawiera tabela 1.5

5

.

Tabela 1.5. Wybrane moduły biblioteki standardowej HLA

Nazwa modułu

Funkcje

args

Procedury przetwarzania argumentów wiersza polecenia wraz z procedurami
pomocniczymi

conv

Procedury konwersji łańcuchów do wartości różnych typów i konwersji odwrotnych

cset

Procedury operujące na zbiorach znaków

DateTime

Procedury manipulowania datą i czasem

excepts

Procedury obsługi wyjątków

fileio

Procedury plikowych operacji wejścia-wyjścia

hla

Specjalne stałe języka HLA i inne definicje

hll

Implementacja instrukcji znanych z języków wysokopoziomowych

Linux

Wywołania systemowe jądra systemu Linux (tylko w wersji HLA dla
systemu Linux)

math

Procedury implementujące obliczenia arytmetyczne o zwiększonej precyzji,
funkcje trygonometrycznych i inne funkcje matematyczne

memory

Procedury przydziału i zwalniania pamięci wraz z procedurami pomocniczymi

misctypes

Definicje rozmaitych typów danych i operacji na tych typach

patterns

Biblioteka procedur dopasowywania wzorców

rand

Implementacja generatora liczb pseudolosowych i procedur pomocniczych

stdin

Procedury obsługi wejścia

stdout

Procedury obsługi wyjścia

stdlib

Specjalny plik nagłówkowy grupujący wszystkie moduły biblioteki standardowej
języka HLA

strings

Biblioteka procedur manipulowania łańcuchami znakowymi

tables

Biblioteka procedur obsługi tablic asocjacyjnych

win32

Definicje stałych wykorzystywanych w wywołaniach systemu Windows
(tylko w wersji HLA dla środowiska Win32)

x86

Stałe i definicje charakterystyczne dla procesora 80x86

                                                          

5

Z racji ciągłego rozwoju biblioteki standardowej języka HLA niniejsza lista nie jest zapewne kompletna.
Aktualnej listy modułów należy szukać w dokumentacji języka HLA.

background image

46

Asembler. Sztuka programowania

Część z tych modułów doczeka się szczegółowego omówienia w dalszej części książki.
Tutaj skupimy się  na najważniejszych — z punktu  widzenia  początkującego  progra-
misty — procedurach biblioteki standardowej HLA, zebranych w module 

.

1.10.1. Stałe predefiniowane w module stdio

Omówienie modułu 

 biblioteki standardowej należałoby rozpocząć od przedsta-

wienia najczęściej wykorzystywanych stałych. Na przykład w następującym kodzie:

()*)&+#82*7=(+2'3

pojawia  się  stała 

  reprezentująca  sekwencję  znaków  przejścia  do  nowego  wiersza.

Identyfikator 

 nie jest w kontekście programu HLA żadnym słowem zarezerwowanym

języka HLA; jego zastosowanie nie ogranicza się też do wywołania 

. Otóż 

to jedynie stała, której wartością jest literał łańcuchowy odpowiadający sekwencji znaków
nowego wiersza (w systemie Windows sekwencja ta obejmuje znaki powrotu karetki
i wysuwu wiersza; w Linuksie to pojedynczy znak wysuwu wiersza).

Oprócz stałej 

 implementacja podsystemu  wejścia-wyjścia w  bibliotece  standardo-

wej HLA zawiera definicje kilku innych przydatnych stałych znakowych. Zostały one
wymienione w tabeli 1.6.

Tabela 1.6. Stałe znakowe definiowane w module wejścia-wyjścia biblioteki standardowej HLA

Stała

Definicja

(, 

Znak dzwonka; wydrukowanie tego znaku objawia się wygenerowaniem dźwięku przez
głośniczek systemowy

(,

Znak cofania kursora

(,

Znak tabulacji

(%

Znak wysuwu wiersza

(

Znak powrotu karetki

Z  wyjątkiem 

  wszystkie  zaprezentowane  tu  stałe  zdefiniowane  są  w  przestrzeni

nazw 

 (stąd wymóg poprzedzania nazw stałych przedrostkiem 

)

6

. Osadzenie

owych stałych w przestrzeni nazw 

 pozwoliło na uniknięcie ewentualnych kon-

fliktów  z  nazwami  zmiennych  definiowanymi  przez  programistę.  Nazwa 

,  jako

używana wyjątkowo często, nie została osadzona w przestrzeni nazw 

 — koniecz-

ność każdorazowego wpisywania 

 byłaby bardzo uciążliwa.

1.10.2. Standardowe wejście i wyjście programu

Wiele procedur wejścia-wyjścia biblioteki HLA jest prefiksowanych nazwą 

 bądź

. Technicznie oznacza to, że nazwy tych procedur zdefiniowane zostały wewnątrz

odpowiedniej przestrzeni nazw. W praktyce zastosowanie przedrostka stanowi rów-
nież sugestię co do pochodzenia danych (dane pobierane ze standardowego urządzenia

                                                          

6

Przestrzenie nazw omówione zostaną w jednym z kolejnych rozdziałów.

background image

Rozdział 1. 

♦ Wstęp do języka asemblerowego

47

wejściowego, 

) bądź ich przeznaczenia (dane  kierowane do standardowego urzą-

dzenia wyjściowego, 

). Najczęściej urządzeniem wejściowym jest klawiatura kon-

soli systemowej. Urządzenie wyjściowe to zwykle ekran konsoli. W ogólności więc in-
strukcje z przedrostkiem 

 bądź 

 realizują odczyty i zapisy danych z  i  do

urządzenia konsoli.

Przy uruchamianiu programu z poziomu wiersza poleceń (zwanego w Linuksie powłoką)
można  dokonać  przekierowania  strumieni  danych  wejściowych  i  wyjściowych  do
urządzeń innych niż domyślne. Na przykład argument wywołania programu w postaci

$.&

 powoduje przekierowanie standardowego wyjścia programu do pliku 

plik_wy.

Z  kolei  argument  wywołania  postaci 

$.

  powoduje  przypisanie  standardowego

wejścia programu do pliku źródłowego 

plik_we. Oto przykłady przekierowania stan-

dardowego  wejścia  i  wyjścia  programu 

%

  uruchamianego  z  poziomu  wiersza

poleceń

7

:

 *=9*)(

 *=9)*)!

 *=9!)!

1.10.3. Procedura stdout.newln

Procedura 

  powoduje  zapisanie  do  urządzenia  standardowego  wyjścia  se-

kwencji  nowego  wiersza.  Wywołanie  owej  procedury  jest  równoważne  wywołaniu

/01

. Wywołanie 

 jest jednak niekiedy nieco wygodniejsze

bądź bardziej czytelne. Oto przykład wywołania procedury:

() .&'3

1.10.4. Procedury stdout.putiN

Procedury  biblioteczne 

2

3

  oraz 

4

  wyprowa-

dzają na standardowe wyjście programu pojedynczą wartość, odpowiednio: 8-bitową,
16-bitową i 32-bitową, interpretowaną jako liczba całkowita ze znakiem. Argumentem
wywołania może być stała, rejestr bądź zmienna w pamięci — rozmiar argumentu musi
jednak odpowiadać rozmiarowi parametru formalnego procedury.

Omawiane  procedury  wyprowadzają  przekazaną  wartość  na  standardowe  wyjście  pro-
gramu. Wyprowadzany napis reprezentujący wartość konstruowany jest z użyciem mini-
malnej liczby znaków pozwalających na dokładne odwzorowanie wartości. Jeśli przeka-
zana argumentem wywołania procedury wartość jest ujemna, napis zostanie poprzedzony
znakiem minusa. Oto kilka przykładów wywołań procedur 

:

()*)&'3

()*)H&(!'3

()*)&;'3

                                                          

7

Uwaga dla użytkowników systemu Linux: w zależności od wartości zmiennej środowiskowej 

5FC

nazwę programu trzeba być może poprzedzić znakami „./”, np. 

I *=9*)(

.

background image

48

Asembler. Sztuka programowania

1.10.5. Procedury stdout.putiNSize

Procedury 

2-

3-

 oraz 

4-

 zapisują do

standardowego urządzenia wyjściowego wartości interpretowane jako liczby całkowite
ze znakiem — a więc działają podobnie jak procedury 

. Tyle że procedury

-

 pozwalają lepiej kontrolować format wyprowadzanych napisów —

programista może odpowiednim argumentem wywołania procedury określić minimalną
szerokość wyprowadzanego napisu. W przypadku kiedy wyprowadzana wartość po kon-
wersji będzie zajmowała mniejszą niż podano liczbę znaków, brakujące znaki uzupeł-
niane są znakami  wypełnienia — znak  wypełnienia również określa się argumentem
wywołania procedury. Składnia wywołania procedur 

-

 jest następująca:

()*) & 22'3

()*)H &!" 22'3

()*) &#$ 22'3

W miejsce parametru 

 można przekazać stałą, nazwę rejestru albo nazwę

zmiennej w pamięci — podany argument musi jednak odpowiadać rozmiarem rozmia-
rowi parametru. Parametr 

 może  przyjmować  argumenty  w  postaci  stałych

całkowitych z zakresu od –256 do 256. Argumentem tego parametru może być również
zawartość rejestru bądź  wartość  zmiennej  przechowywanej  w  pamięci.  Argumentem
parametru 

 powinien być pojedynczy znak.

Podobnie jak procedury 

 procedury 

-

 wyprowadzają na

urządzenie standardowego wyjścia napis reprezentujący przekazaną w wywołaniu wartość
liczbową interpretowaną jako liczbę całkowitą ze znakiem. W tej  wersji programista
może jednak określić szerokość napisu — określa ją jako minimalną liczbę znaków, jaką
powinien zajmować napis. Jeśli  wyświetlana wartość zajmować będzie więcej znaków
(na przykład przy próbie wyświetlenia wartości 1234 w polu o szerokości 2), to wypro-
wadzony napis zostanie stosownie poszerzony. Jednak w przypadku, gdy liczba znaków
w napisie jest mniejsza od założonego minimum, brakujące znaki są w kodzie procedury
uzupełniane  znakami  wypełnienia.  Kiedy  wyprowadzana  wartość  argumentu 

 jest liczbą ujemną, napis zostanie wyrównany w ramach wyprowadzanego pola do

lewej; wartość dodatnia powoduje wyrównywanie napisów do prawej.

Jeśli  wartość  bezwzględna  parametru 

  jest  większa  niż  minimalna  liczba

znaków  potrzebnych  do  dokładnego  reprezentowania  wyprowadzanej  wartości,  pro-
cedury 

-

 uzupełniają napis znakami  wypełnienia, dodając je z lewej

bądź z prawej strony napisu (przed bądź za znakową reprezentacją liczby). Znak wy-
pełnienia  definiowany  jest  argumentem 

.  W  zdecydowanej  większości

przypadków znakiem tym jest znak spacji, jednak w sytuacjach szczególnych konieczne
może okazać się wypełnienie innym znakiem. Należy tylko pamiętać, że argument pa-
rametru 

 powinien być wartością znakową. W języku HLA stałe znakowe

ujmowane są w znaki pojedynczego cudzysłowu. W miejsce tego parametru można też
wskazać ośmiobitowy rejestr.

Listing  1.4  prezentuje  prosty  program  w  języku  HLA  demonstrujący  sposób  wyko-
rzystania  procedury 

4-

  w  celu  wyświetlenia  listy  wartości  w  formie

tabelarycznej.

background image

Rozdział 1. 

♦ Wstęp do języka asemblerowego

49

Listing 1.4. Wyprowadzenie kolumn wartości liczbowych — zastosowanie procedury stdout.puti32Size

*=9/)9?)93

J)( &+(,##%+'

-3

??-3

, =/)9?)93

9&KH2'3

9&"2??'3

.# &"'(

%&??'#

() .&'3

9&"2??'3

 (%3

()*) &22$$'3

),&2'3

((&2??'3

 (.# 3

() .&'3

 (/)9?)93

1.10.6. Procedura stdout.put

Procedura 

8

  to  najelastyczniejsza  procedura  wyprowadzania  napisów  na

wyjście programu dostępna w module obsługi standardowego wyjścia. Procedura ta łączy
funkcje wszystkich pozostałych procedur wyjścia  standardowego, udostępniając pro-
gramiście elegancki i efektywny mechanizm wyprowadzania danych.

W  najbardziej  ogólnej  postaci  składnia  wywołania  procedury 

  prezentuje

się następująco:

()*)&%'3

Lista  argumentów  wywołania  procedury 

  może  zostać  konstruowana  ze

stałych, rejestrów i zmiennych; kolejne argumenty oddziela się przecinkami. Procedura
wyprowadza na standardowe wyjście wartości wszystkich kolejnych argumentów wywo-
łania. Jako że procedura ta była już wielokrotnie prezentowana w przykładach, Czytelnik
orientuje się, przynajmniej w zakresie podstawowym, co do sposobu jej wywoływania.

                                                          

8

Tak naprawdę 

()*)

 to makrodefinicja, a nie procedura. Rozróżnienie pomiędzy makrodefinicją

a procedurą wykracza jednak poza zakres niniejszego rozdziału; zostanie ono uwzględnione w dalszej
części książki.

background image

50

Asembler. Sztuka programowania

Warto jedynie podkreślić, że niniejsza procedura udostępnia programiście szereg wła-
ściwości, które nie były dotąd prezentowane w przykładach. W szczególności każdy
z argumentów wywołania może być zadany w jednej z dwóch postaci:

-

 może być dowolną stałą, rejestrem albo zmienną przechowywaną w pamięci.

W niniejszym rozdziale prezentowane były argumenty w postaci literałów łańcuchowych
i zmiennych  w pamięci. Jak dotychczas  wszystkie argumenty wywołania odpowiadały
pierwszej możliwej postaci argumentu. W postaci drugiej programista może po dwu-
kropku  określić  minimalną  szerokość  napisu  reprezentującego  wartość  —  szerokość
ta  interpretowana  jest  podobnie  jak  w  procedurach 

-

9

.  Przykładowy

program  z  listingu  1.5  generuje  wydruk  podobny  do  tego  tworzonego  programem

5()

,  tyle  że  tutaj  miejsce  procedury 

-

  zajęła  procedura

.

Listing 1.5. Demonstracja sposobu określenia szerokości napisów w argumentach procedury stdout.put

*=9/)9?)93

J)( &+(,##%+'

-3

??-3

, =/)9?)93

9&KH2'3

9&"2??'3

.# &"'(

%&??'#

() .&'3

9&"2??'3

 (%3

()*)&-'3

),&2'3

((&2??'3

 (.# 3

() .&'3

 (/)9?)93

Procedura 

 obsługuje jednak więcej atrybutów. Będą one omawiane w kolej-

nych rozdziałach, w miarę potrzeby ich wprowadzania do programów przykładowych.

                                                          

9

W odróżnieniu od procedur stdout.putiNSize nie da się tu określić dowolnego znaku wypełnienia
— znakiem tym jest domyślnie znak spacji. W obliczu konieczności zastosowania innego wypełnienia
należy skorzystać z procedur stdout.putiNSize.

background image

Rozdział 1. 

♦ Wstęp do języka asemblerowego

51

1.10.7. Procedura stdin.getc

Procedura 

%!

 wczytuje pojedynczy znak z bufora urządzenia standardowego

wejścia

10

. Znak ten umieszczany jest po wyjściu z procedury w rejestrze AL. Zastosowa-

nie procedury ilustruje program z listingu 1.6.

Listing 1.6. Demonstracja działania procedury stdin.getc

*=9#*)3

J)( &+(,#%%+'

) -3

, =#*)3

// Poniższa pętla wykonywana jest dopóty,

// dopóki użytkownik potwierdza wykonanie kolejnej iteracji.

 * 

// Wyprowadź 14 wartości.

9&L2) '3

.# &) "'(

()*)&) -'3

),&2) '3

 (.# 3

// Zaczekaj, aż użytkownik naciśnie 't' albo 'n'.

()*)&22+6744 8 8 &I'M-+'3

%  

( (1&'3

(= &'3

, 4%&$$'3

, 4%&$$'3

()*)&+5 .*.(,$$2,$$-+'3

 (%3

() .&'3

)&$$'3

 (#*)3

                                                          

10

Bufor to w tym kontekście ładniejsza nazwa tablicy.

background image

52

Asembler. Sztuka programowania

W  powyższym  programie  wywołanie  procedury 

6

  służy  do  wymuszenia

przejścia do nowego wiersza strumienia wejściowego. Szczegółowy opis tej procedury
znajduje się nieco dalej, w niniejszym rozdziale.

1.10.8. Procedury stdin.getiN

Procedury 

%2

%3

 oraz 

%4

 realizują operację wczytania

z urządzenia standardowego wejścia odpowiednio: 8-bitowej, 16-bitowej i 32-bitowej
wartości liczbowej całkowitej. Wczytane wartości umieszczane są w rejestrach AL, AX
bądź EAX. Niniejsze procedury stanowią standardowy sposób wczytywania liczb całko-
witych ze znakiem do programów języka HLA.

Podobnie jak procedura 

%!

, procedury 

%

 wczytują ze standardowego

wejścia sekwencję znaków. W sekwencji tej ignorowane są wszelkie początkowe znaki
odstępów (spacje, znaki tabulacji i tak dalej); reszta sekwencji (zawierająca cyfry poprze-
dzone opcjonalnym znakiem minusa) konwertowana jest do postaci liczbowej. W przy-
padku, kiedy sekwencja wejściowa zawiera znaki inne niż dozwolone lub wprowadzona
wartość nie mieści się w zakresie liczbowym charakterystycznym dla danej procedury,
zgłaszany  jest  wyjątek  (można  go  przechwycić,  umieszczając  wywołanie  procedury
w bloku kodu chronionego w instrukcji 

&&

). Procedura 

%2

 obsługuje

liczby  z  zakresu  od  –128  do  127, 

%3

  —  od  –32  768  do  32  767;  wartości

wczytywane do programu za pośrednictwem procedury 

%4

 muszą zaś mieścić

się w przedziale od –2 147 483 648 do 2 147 483 647.

Sposób wykorzystania owych procedur ilustruje listing 1.7.

Listing 1.7. Przykład wykorzystania wywołań stdin.getiN

*=9*)3

J)( &+(,##%+'

-

-3

H-H3

-3

, =*)3

// Pobierz od użytkownika kilka liczb całkowitych różnej wielkości.

()*)&+6*.( . 4, 4.&4 )N'-+'3

(= &'3

9&2'3

()*)&+6*.(. 4, 4.&4 )NHNHN'-+

'3

(= H&'3

9&!2H'3

()*)&+6*.(, 4.&4 )((9(.'-+'3

(= &'3

background image

Rozdział 1. 

♦ Wstęp do języka asemblerowego

53

9& !2'3

// Wyświetl podane wartości.

()*)

&

2

+@.*.( ,7-+222

+,.,4.-+2-22

+H,.,4.-+2H-22

+,.,4.-+2-2

'3

 (*)3

Warto skompilować i uruchomić powyższy program i sprawdzić, co się stanie, jeśli wpro-
wadzane liczby będą wykraczały poza dozwolony zakres albo ciągi wartości zawierać
będą niedozwolone znaki.

1.10.9. Procedury stdin.readLn i stdin.flushInput

Moment wywołania procedury 

%!

 czy 

%4

 nie musi być tożsamy z mo-

mentem wprowadzenia danych przez użytkownika. Biblioteka standardowa języka HLA
buforuje  dane  wprowadzane  na  standardowe  wejście,  wczytując  jednorazowo  cały
wiersz tekstu wprowadzanego na to wejście. Wywołanie procedury wejścia może więc
zostać zrealizowane za pośrednictwem operacji odczytu danych z bufora wejściowego
(o ile ten nie jest pusty). Choć buforowanie w ogólności poprawia efektywność mani-
pulowania danymi wejściowymi, niekiedy wprowadza zamieszanie, jak w poniższym
przykładzie:

()*)&+6*.( . 4, 4.&4 )N'-+'3

(= &'3

9&2'3

()*)&+6*.(. 4, 4.&4 )NHNHN'-+'3

(= H&'3

9&!2H'3

Programista ma nadzieję, że wykonanie powyższego kodu da następujący efekt: program
wyświetli  monit  o  podanie  liczby,  zaczeka  na  wprowadzenie  jej  przez  użytkownika,
wyświetli kolejny monit i również zaczeka na wprowadzenie liczby. W rzeczywistości
program może mieć nieco inny przebieg. Jeśli, na przykład, powyższy kod zostanie uru-
chomiony, a użytkownik w odpowiedzi na pierwszy monit wprowadzi łańcuch „123 456”
program nie będzie już po wyświetleniu drugiego monitu oczekiwał na wprowadzenie
danych — procedura 

%3

 odczyta po prostu drugi z wprowadzonych do bufora

wejściowego łańcuchów (456).

Procedury wejścia oczekują na wprowadzenie tekstu przez użytkownika jedynie wtedy,
kiedy bufor  wejściowy jest pusty. Dopóki zawiera on  jakiekolwiek  znaki,  procedury
wejścia ograniczają się do odczytu z bufora. Można to  wykorzystać, konstruując na-
stępujący kod:

background image

54

Asembler. Sztuka programowania

()*)&+6*.((. ,74. -+'3

(= &'3

9& !2'3

(= &'3

9& !2# ;'3

Tutaj  zezwala  się  wprost  użytkownikowi  na  jednorazowe  (w  ramach  pojedynczego
wiersza) wprowadzenie dwóch liczb. Liczby te powinny zostać oddzielone jednym bądź
kilkoma znakami odstępu. Pozwala to na zaoszczędzenie miejsca na ekranie. Tutaj
buforowanie danych  wejściowych okazało  się  korzystne.  Kiedy  indziej  jednak  może
być odwrotnie.

Na szczęście biblioteka standardowa języka HLA przewiduje dwie procedury sterujące
działaniem bufora standardowego urządzenia wejściowego. Są to procedury 

6

oraz 

(

. Pierwsza z nich usuwa z bufora  wszystko, co zostało  w  nim

umieszczone wcześniej, i tym samym wymusza wprowadzenie przez użytkownika nowe-
go wiersza danych. Druga po prostu usuwa całą zawartość bufora. Oczyszczenie bufora
powoduje,  że  przy  następnym  wywołaniu  dowolnej  z  procedur  wejścia,  użytkownik
będzie  musiał  wprowadzić  nowy  wiersz  danych.  Procedurę 

6

  wywoł uje

się  zwykle  bezpośrednio  przez  wywołaniem  dowolnej  procedury  wejścia.  Procedura

(

  wywoływana  jest  zaś  typowo  bezpośrednio  po  odczytaniu  danych

z bufora wybraną procedurą wejścia.

Jeśli  w  programie  wykorzystywana  jest  procedura 

6  i  okaże  się,  że

konieczne jest dwukrotne wprowadzenie danych, to należy zastanowić się nad wyko-
rzystaniem w jej miejsce procedury 

(. W ogólności ta ostatnia proce-

dura,  pozwalająca  na  opróżnienie  bufora  przez  kolejną  operacją  wejścia,  jest  wy-
korzystywana  znacznie  częściej.  Konieczność  zastosowania  procedury 

6

jest bardzo rzadka — należy ją stosować wyłącznie tam, gdzie konieczne jest wymu-
szenie na użytkowniku wprowadzenia nowego, aktualnego wiersza danych.

1.10.10. Procedura stdin.get

Procedura 

%

 łączy w sobie przedstawione wcześniej procedury wejścia, udo-

stępniając ich funkcje za pośrednictwem pojedynczego wywołania. Procedura 

%

jest przy tym nieco prostsza  w  użyciu  niż 

, ponieważ jedynymi argumen-

tami wywołania tej pierwszej są nazwy zmiennych (lub rejestrów), w których mają
zostać umieszczone wczytane wartości.

Przyjrzyjmy się ponownie przykładowi z poprzedniego punktu:

()*)&+6*.((. ,74. -+'3

(= &'3

9& !2'3

(= &'3

9& !2# ;'3

Równoważny przykład wykorzystujący procedurę 

%

 wyglądałby następująco:

()*)&+6*.((. ,74. -+'3

(= &2# ;'3

background image

Rozdział 1. 

♦ Wstęp do języka asemblerowego

55

Jak widać, zastosowanie procedury 

%

 może program skrócić i zwiększyć jego

czytelność.

Należy przy tym pamiętać, że wywołanie procedury 

%

 powoduje umieszczenie

wczytanych wartości od razu we wskazanych zmiennych; żadne z wczytanych wartości
nie pojawią się w rejestrach, chyba że rejestry te wystąpią na liście argumentów wywo-
łania. Argumentami  wywołania  mogą być zarówno nazwy  zmiennych,  jak  i  nazwy
rejestrów.

1.11. Jeszcze o ochronie wykonania
kodu w bloku try..endtry

Jak  zapewne  Czytelnik  pamięta,  instrukcje 

&&

  otaczają  blok  kodu,  którego

wykonanie może potencjalnie sprowokować wyjątki zakłócające działanie programu.
Wyjątki mogą mieć trzy źródła: mogą być zgłaszane sprzętowo (np. w przypadku dzie-
lenia przez zero), generowane przez system operacyjny albo podnoszone wykonaniem
odpowiedniej instrukcji języka HLA. Programista może przechwytywać i obsługiwać
sytuacje wyjątkowe, umieszczając stosowny kod w ramach klauzuli 

!

. Typowy

przykład zastosowania instrukcji 

&&

 ilustruje listing 1.8.

Listing 1.8. Przykład zastosowania bloku try..endtry

*=9 (*)3

J)( &+(,##%+'3

-

)-)H3

, = (*)3

7

()*)&+6*.(, 4., 4)-+'3

(= &)'3

()*)&+6*.(-+2)2'3

 ! *& !? '

()*)&+6*.(7=.  (. 4+'3

 ! *& !;) @)@%A= '

()*)&+6*.(,8 ,7()+'3

 (73

 ( (*)3

background image

56

Asembler. Sztuka programowania

W  języku  HLA  instrukcje  znajdujące  się  za  słowem 

&

,  a  przed  pierwszą  klauzulą

!

  noszą  miano  instrukcji  chronionych.  Jeśli  podczas  wykonywania  takich

instrukcji zgłoszony zostanie wyjątek, sterowanie zostanie przekazane do pierwszej
klauzuli 

!

 skojarzonej z danym wyjątkiem, przeszukując kolejne klauzule pod

kątem zgodności identyfikatora wyjątku zadeklarowanego ze zgłoszonym

11

. Identyfi-

kator wyjątku to po prostu 32-bitowa liczba. Stąd również każda wartość umieszczana
w  nawiasach  klauzuli 

!

  powinna  być  wartością  32-bitową.  Predefiniowane

wartości wyjątków języka HLA wymienione są w pliku 

excepts.hhf. I choć byłoby to nie-

wątpliwie rażącym naruszeniem przyjętego powszechnie stylu programowania, w  klau-
zulach 

!

 dozwolone jest podawanie wprost  wartości numerycznych wyjątków

bez korzystania z predefiniowanych nazw stałych.

1.11.1. Zagnieżdżone bloki try..endtry

Jeśli w wyniku przeszukania klauzul 

!

 nie nastąpi dopasowanie identyfikatora

wyjątku, będzie miało miejsce przeszukanie klauzul nadrzędnego bloku 

&&

,

w  którym blok bieżący jest zagnieżdżony dynamicznie.  Spójrzmy  na  przykład  z  li-
stingu 1.9.

Listing 1.9. Zagnieżdżanie instrukcji try..endtry

*=9 (*)3

J)( &+(,##%+'3

-

)-)H3

, = (*)3

7

7

()*)&+6*.(, 4., 4)-+'3

(= &)'3

()*)&+6*.(-+2)2'3

 ! *& !? '

()*)&+6*.(7=.  (. 4+'3

 (73

()*)&+( .74* *  4 )+'3

 ! *& !;) @)@%A= '

                                                          

11

W programie HLA identyfikator ten umieszczany jest w rejestrze EAX. Stąd po przekazaniu sterowania
do odpowiedniej klauzuli 

 ! *

 można odwoływać się do identyfikatora wyjątku, odwołując się

do rejestru EAX.

background image

Rozdział 1. 

♦ Wstęp do języka asemblerowego

57

()*)&+6*.(,8 ,7()+'3

 (73

 ( (*)3

Na listingu 1.9 widać zagnieżdżone dwa bloki instrukcji 

&&

. Jeśli w ramach

wykonania instrukcji 

%

 użytkownik wprowadzi wartość większą od 4 miliardów

z kawałkiem, procedura 

%

 podniesie wyjątek 

++,%

. Kiedy wyjątek

ten zostanie przekazany do systemu wykonawczego HLA, nastąpi przeszukanie klauzul

!

 bieżącego bloku 

&&

 (tego, w którym nastąpiło zgłoszenie wyjątku;

w prezentowanym przykładzie jest to blok zagnieżdżony). Jeśli nie uda się dopasować
identyfikatora  wyjątku 

++,%

  w  bieżącym  bloku 

&&

,  system

wykonawczy  HLA  sprawdzi,  czy  bieżący  blok 

&&

  nie  został  zagnieżdżony

w innym bloku 

&&

 (co akurat ma miejsce na listingu 1.9). W takim przypadku

system  wykonawczy HLA przeszuka również  klauzule 

!

 bloku zewnętrznego

(nadrzędnego). Na listingu 1.9 ów blok zawiera klauzulę z odpowiednią wartością iden-
tyfikatora, więc sterowanie przekazywane jest do pierwszej instrukcji umieszczonej za
klauzulą 

!/++,%0

.

Po opuszczeniu bloku 

&&

 system wykonawczy traktuje ów blok jako nieak-

tywny i jeśli teraz nastąpi zgłoszenie  wyjątku,  system  wykonawczy  HLA  nie  będzie
przeszukiwał  klauzul 

!

  tego  bloku

12

.  Pozwala  to  na  różnicowanie  procedur

obsługi wyjątków w różnych miejscach programu.

Jeśli  ten  sam  wyjątek  obsługują  dwa  bloki 

&&

,  a  jeden  z  tych  bloków  jest

zagnieżdżony  w  bloku  chronionym  pierwszego,  to  w  momencie  zgłoszenia  wyjątku
podczas  wykonywania  wewnętrznego  bloku  chronionego  system  wykonawczy  HLA
przekaże sterowanie do kodu obsługi  wyjątku  w  wewnętrznym bloku 

&&

.

Kod obsługi tego samego wyjątku w zewnętrznym bloku jest ignorowany — HLA nie
przekazuje sterowania do tego kodu.

W przykładzie z listingu 1.9 druga instrukcja 

&&

 została statycznie zagnież-

dżona w pierwszym bloku 

&&

13

. Wcześniej wspomniano, że jeśli bieżący blok

&&

  nie  zawiera  kodu  obsługi  zgłoszonego  wyjątku,  następuje  przeszukanie

klauzul 

!

 kolejnych bloków 

&&

, w których blok bieżący został zagnież-

dżony dynamicznie. W przypadku zagnieżdżenia dynamicznego zagnieżdżenie nie wy-
nika wprost ze struktury kodu źródłowego. Sterowanie może zostać przekazane z wnętrza
bloku 

&&

 do zupełnie innego miejsca programu, niżby to wynikało z samej

struktury  kodu.  Jeśli  w  tamtym  miejscu  realizowany  jest  blok  chroniony  instrukcji

&&

, to mamy do czynienia z zagnieżdżeniem dynamicznym. Zagnieżdżenie dy-

namiczne można zrealizować na kilka sposobów, a  najbardziej chyba intuicyjnym (i za-
pewne  znanym  Czytelnikowi  z  języków  wysokiego  poziomu)  z  nich  jest  wywołanie

                                                          

12

Chyba że sterowanie zostanie wcześniej przekazane z powrotem do bloku 

7 (7

, na przykład

kiedy jest on umieszczony w ciele pętli.

13

Zagnieżdżenie statyczne należy rozumieć jako „fizyczne”, bo wynikające wprost ze struktury kodu
źródłowego, umieszczenie jednego bloku wewnątrz drugiego. Kiedy mowa o zagnieżdżaniu instrukcji,
zwykle chodzi o zagnieżdżanie statyczne.

background image

58

Asembler. Sztuka programowania

procedury. Kiedy więc w dalszej części książki omawiany będzie sposób pisania i wywo-
ływania procedur w języku asemblerowym, należy pamiętać, że wywołanie procedury
z  bloku  kodu  chronionego  może  doprowadzić  do  dynamicznego  zagnieżdżenia  bloków

&&

  —  wystarczy,  aby  procedura  również  zawierała  blok 

&&

  (i  aby

został on wykonany).

1.11.2. Klauzula unprotected bloku try..endtry

Wykonywanie instrukcji 

&

 powoduje zawsze zachowanie bieżącego środowiska wyjąt-

ków i przygotowanie systemu wykonawczego (na wypadek zgłoszenia wyjątku) do prze-
kazania sterowania do odpowiedniej klauzuli 

!

. Jeśli program przebrnie przez

blok instrukcji chronionych, pierwotne środowisko wyjątków jest przywracane, a ste-
rowanie  przekazywane  jest  do  pierwszej  instrukcji  znajdującej  się  za  klauzulą 

&

.

Bardzo ważną rolę w tej procedurze ma ostatni jej etap, czyli przywrócenie pierwot-
nego  środowiska  wyjątków.  W  przypadku  jego  pominięcia  wszelkie  kolejne  wyjątki
powodowałyby przekazanie sterowania do opuszczonego już bloku 

&&

. Problem

ten ilustruje listing.1.10.

Listing 1.10. Niepoprawne opuszczenie bloku instrukcji try..endtry

*=9 (*)L3

J)( &+(,##%+'

*)-)3

, = (*)L3

// Poniższa pętla jest kontynuowana dopóty, dopóki użytkownik wprowadza

// nieprawidłową liczbę; wyjście z pętli następuje w wyniku wykonania instrukcji break.

%  

7

()*)&+5(8, 4.-+'3

(= &*)'3

()*)&+6*.(-+2*)2'3

, 43

 ! *& !;) @)@%A= '

()*)&+1,8 ,7()3*..*.( +'3

 ! *& !? '

()*)&+?=.  (. 43*..*.( +

'3

 (73

 (%3

background image

Rozdział 1. 

♦ Wstęp do języka asemblerowego

59

// Uwaga: poniższy kod znajduje się poza pętlą i nie jest osadzony w bloku chronionym

// instrukcji try..endtry

()*)&+5(88  8 (, -+'3

(= &*)'3

()*)&+/.,-+2*)2'3

 ( (*)L3

Niniejszy przykład z pozoru implementuje niezawodny, odporny na błędy system wpro-
wadzania danych: wokół bloku kodu chronionego rozciągnięta jest pętla wymuszająca
ponowne  wprowadzenie  danych  przez  użytkownika,  jeśli  w  poprzedniej  próbie  nie
udało się ich poprawnie odczytać. Pomysł jest dobry; problem tkwi w jego nieprawi-
dłowej realizacji. Otóż instrukcja 

$

 powoduje opuszczenie pętli 

bez  przywrócenia  poprzedniego  stanu  środowiska  wyjątków.  Z  tego  względu,  kiedy
program wykonuje procedurę 

%

 umieszczoną poza pętlą (a tym bardziej poza

blokiem kodu chronionego), system obsługi wyjątków wciąż traktuje jej kod jako chro-
niony. Jeśli więc przy okazji jego wykonania zgłoszony zostanie wyjątek, sterowanie
zostanie  przekazane  z  powrotem  do  stosownej  klauzuli 

!

  opuszczonego  już

przecież  bloku 

&&

.  Jeśli  identyfikatorem  wyjątku  będzie 

++,%

albo 

)*

,  wyświetlony  zostanie  stosowny  komunikat,  a  użytkownik

ponownie zmuszony będzie do wprowadzenia liczby. Tego zaś programista raczej
nie przewidywał.

Przekazanie  sterowania  do  nieodpowiedniego  bloku 

&&

  to  tylko  część  pro-

blemu. Kod z listingu 1.10 obarczony jest inną poważną wadą, związaną ze sposobem
zachowywania i następnie przywracania przez system wykonawczy HLA środowiska
wyjątków.  W  szczególności  bowiem  HLA  zachowuje  informacje  o  stanie  systemu
wyjątków w specjalnym obszarze pamięci zwanym stosem. Jeśli po  wyjściu z bloku

&&

  stan  środowiska  wyjątków  nie  zostanie  przywrócony,  informacje  o  tym

środowisku pozostaną na stosie; obecność na nim nadmiarowych danych może zaś do-
prowadzić do błędnego działania programu.

Jest już chyba jasne, że program nie powinien opuszczać bloku 

&&

 z pomi-

nięciem  etapu  przywrócenia  pierwotnego  stanu  środowiska  wyjątków.  Wiadomo,  że
sposób wprowadzania danych zaprezentowany na listingu 1.10 jest niepoprawny, ale
z drugiej strony sama koncepcja wymuszania powtarzania operacji wprowadzania aż
do skutku jest jak najbardziej słuszna. Można ją zaimplementować przy użyciu specjal-
nej klauzuli bloku 

&&

. Spójrzmy na kod z listingu 1.11.

Listing 1.11. Blok pozbawiony ochrony wewnątrz bloku try..endtry

*=9 (*)3

J)( &+(,##%+'

*)-)3

, = (*)3

background image

60

Asembler. Sztuka programowania

// Poniższa nieskończona pętla wykonywana jest dopóty, dopóki użytkownik wprowadza

// niepoprawne dane. Wyjście z pętli następuje w wyniku wykonania instrukcji break.

// Instrukcja ta, mimo że obecna wewnątrz bloku try..endtry, znajduje się w bloku

// kodu niechronionego sygnalizowanego klauzulą unprotected.

%  

7

()*)&+5(8, 4.-+'3

(= &*)'3

()*)&+6*.(-+2*)2'3

)*  (

, 43

 ! *& !;) @)@%A= '

()*)&+1,8 ,7()3*..*.( +'3

 ! *& !? '

()*)&+?=.  (. 43*..*.( +

'3

 (73

 (%3

// Uwaga: poniższy kod znajduje się poza pętlą i nie jest osadzony w bloku chronionym

// instrukcji try..endtry

()*)&+5(88  8 (, -+'3

(= &*)'3

()*)&+/.,-+2*)2'3

 ( (*)3

Osiągnięcie  klauzuli 

!

  inicjuje  operację  przywrócenia  pierwotnego  stanu

środowiska wyjątków. Oczywiste jest, że po przejściu do bloku kodu niechronionego
wykonanie  kodu  nie  jest  już  chronione  na  wypadek  zgłoszenia  wyjątków.  Klauzula

!

 nie znosi jednak ochrony kodu realizowanej w dynamicznie zagnieżdżo-

nych zewnętrznych blokach 

&&

 — klauzula 

!

 odnosi się jedynie

do  tego  bloku 

&&

,  w  którym  została  umieszczona.  Jako  że  na  listingu  1.11

instrukcja przerywająca pętlę, 

$

, znajduje się za klauzulą 

!

, przekazanie

sterowania poza pętlę odbywa się po uprzednim przywróceniu stanu wyjątków.

Słowo 

!

, jeśli już występuje w ramach instrukcji 

&

, powinno znajdować

się bezpośrednio za blokiem kodu chronionego. Musi więc poprzedzać wszystkie klau-
zule 

!

.

background image

Rozdział 1. 

♦ Wstęp do języka asemblerowego

61

W obliczu zgłoszenia wyjątku system wykonawczy HLA automatycznie przywraca
pierwotny stan środowiska wykonania. Z tego względu w ramach kodu obsługi wyjątku,
po klauzuli 

!

 można swobodnie korzystać z instrukcji 

$

, nie obawiając się

o negatywne efekty charakterystyczne dla niepoprawnego opuszczenia bloku 

&&

.

Z racji przywrócenia pierwotnego stanu wyjątków w bloku niechronionym oraz w kodzie
obsługi wyjątku po klauzuli 

!

, wystąpienie wyjątku w jednym z tych obszarów

powoduje natychmiastowe przekazanie sterowania do zewnętrznego bloku 

&&

,

w którym blok bieżący został dynamicznie zagnieżdżony. W przypadku braku takiego
bloku program jest awaryjnie przerywany, a na  standardowe  wyjście  wyprowadzany
jest stosowny komunikat o błędzie.

1.11.3. Klauzula anyexception bloku try..endtry

Typowo instrukcji 

&

  towarzyszy  szereg  klauzul 

!

  obsługujących  wszelkie

wyjątki,  których  wystąpienie  w  kodzie  chronionym  zdoła  przewidzieć  programista.
Niekiedy to nie wystarcza — konieczne jest wtedy zapewnienie obsługi wszystkich moż-
liwych (a nie tylko przewidzianych) wyjątków, aby program nie został przedwcześnie
zakończony. Jeśli programista jest autorem całości kodu bloku chronionego, powinien
być zdolny do przewidzenia wszystkich sytuacji wyjątkowych związanych potencjalnie
z wykonaniem bloku. Jeśli jednak w bloku tym znajdują się wywołanie procedury biblio-
tecznej,  wywołanie  funkcji  interfejsu  systemu  operacyjnego  czy  inne  instrukcje,  nie
w pełni kontrolowane przez programistę, przewidzenie wszystkich możliwych wyjątków
może  być  niemożliwe.  Tymczasem  zgłoszenie  wyjątku  o  identyfikatorze  innym  niż
identyfikatory podane w kolejnych klauzulach 

!

 może doprowadzić do załamania

programu.  Szczęśliwie  język  HLA  przewiduje  umieszczenie  w  bloku 

&&

klauzuli 

&!

, do której dopasowywane są wszystkie wyjątki, których identy-

fikatorów nie uda się odpasować do klauzul 

!

.

Klauzula 

&!

 nie różni się wiele od klauzuli 

!

 — jedyna odmienność

tkwi w braku konieczności określania identyfikatora wyjątku (to oczywiste). Jeśli obok
klauzul 

!

  w  bloku 

&&

  znajduje  się  klauzula 

&!

,  powinna

ona być ostatnią klauzulą obsługi wyjątków w bloku. Dopuszczalne jest naturalnie, aby
klauzula 

&!

 była jedyną klauzulą obsługi wyjątków w bloku 

&&

.

Po  przekazaniu  sterowania  do  klauzuli 

&!

  identyfikator  wyjątku  umiesz-

czany jest w rejestrze EAX. Identyfikator ten można w ramach kodu obsługi wyjątku
sprawdzać, dopasowując obsługę do przyczyny zgłoszenia wyjątku.

1.11.4. Instrukcja try..endtry i rejestry

Każdorazowe wkroczenie do bloku 

&&

 oznacza konieczność zachowania na

stosie 16 bajtów informacji o stanie środowiska wyjątków. Przywrócenie środowiska
wyjątków po opuszczeniu tego bloku (albo w wyniku osiągnięcia klauzuli 

!

)

wymaga odczytania właśnie owych 16 bajtów. Do momentu zgłoszenia wyjątku reali-
zowanie kodu chronionego nie wpływa na zawartość żadnych rejestrów. Sytuacja zmienia
się w momencie zgłoszenia wyjątku w wyniku wykonania instrukcji kodu chronionego.

background image

62

Asembler. Sztuka programowania

Po przejściu do klauzuli 

!

 rejestr EAX zawiera identyfikator (numer) wyjątku.

Reszta  rejestrów  ogólnego  przeznaczenia  zawiera  wartości  nieokreślone.  Ponieważ
system  operacyjny  może  zgłosić  wyjątek  w  reakcji  na  błąd  sprzętowy,  nie  powinno
się nawet zakładać, że rejestry ogólnego przeznaczenia zawierać będą po rozpoczęciu
obsługi wyjątku wartości, które znajdowały się w nich pierwotnie. Kod generowany przez
system HLA do obsługi wyjątków może być różny w różnych wersjach kompilatora,
więc poleganie  na zawartości rejestrów  w  kodzie  obsługi  wyjątków  jest  co  najmniej
ryzykowne.

Jako że po przejściu do bloku kodu obsługi wyjątku nie można czynić żadnych założeń
co do zawartości rejestrów (z wyjątkiem rejestru EAX), to jeśli kod kontynuowany za
klauzulą 

&

  zakłada  obecność  w  rejestrach  jakichś  konkretnych  wartości  (np.

wartości umieszczonych tam przed wkroczeniem do bloku kodu chronionego), należy te
wartości przywrócić samodzielnie. Zaniedbanie tego może doprowadzić do błędnego
działania programu, przy czym błędy tego rodzaju są tym trudniejsze do wykrycia, że
sytuacje wyjątkowe z definicji zdarzają się rzadko, co utrudnia odtworzenie i diagnostykę
błędu; dodatkowym utrudnieniem jest to, że nie zawsze zgłoszenie wyjątku musi zmienić
wartość konkretnego rejestru. Sposób rozwiązania problemu zachowania wartości reje-
strów prezentuje następujący fragment kodu:

)9-3

O

9&"2)9'3

%&9&"2 ,!'3 ,!3& ,!''(

*)#& ,!'3

// Zachowanie wartości rejestru EBX na wypadek wyjątku.

%  

7

(= &'3

)*  (, 43

 ! *& !? '

()*)&+/ (..PQ3*,)8*. -+'3

 (73

 (%3

**& ,!'3

// Przywrócenie zawartości rejestru EBX.

((& ,!2 !'3

((& !2)9'3

 (%3

Ponieważ mechanizm obsługi wyjątków powoduje potencjalne zmiany  wartości reje-
strów i ponieważ obsługa wyjątków to proces stosunkowo nieefektywny, nie powinno się
stosować instrukcji 

&

 w roli zwykłej struktury sterującej wykonaniem programu (na

przykład symulując w bloku 

&&

 działanie instrukcji wyboru znanych z języków

wysokiego poziomu, jak 

!

 czy 

!

). Takie praktyki  wpływają ujemnie  na  wy-

dajność programu; mogą tez wprowadzać niepożądane, trudne do wykrycia i zdiagno-
zowania efekty uboczne wynikające z zakłócania zawartości rejestrów.

background image

Rozdział 1. 

♦ Wstęp do języka asemblerowego

63

Działanie mechanizmu  wyjątków opiera się na założeniu,  że  rejestr  EBP  wykorzy-
stywany  jest  wyłącznie  w  roli  wskaźnika  rekordów  aktywacji  (rekordy  aktywacji
omawiane są w rozdziale poświęconym procedurom). Standardowo programy HLA
wykorzystują  ów  rejestr  właśnie  w  tej  roli.  Warunkiem  poprawnego  działania  pro-
gramów jest  więc  unikanie  modyfikowania  zawartości  rejestru  EBP. Jeśli  rejestr  ten
zostanie użyty  w roli  rejestru  ogólnego przeznaczenia,  na przykład  w  obliczeniach
arytmetycznych, obsługa  wyjątków  systemu  wykonawczego HLA nie będzie działać
poprawnie, pojawić się też mogą dodatkowe problemy. To samo tyczy się zresztą re-
jestru  ESP  —  jego  również  nie  należy  wykorzystywać  jako  rejestru  ogólnego  prze-
znaczenia.