2.4 Skoki warunkoweInstrukcje skoków nie są szczególnie lubiane przez programistów języków wysokiego poziomu dlatego, że nieodpowiednie ich użycie może doprowadzić do zawieszenia się programu po przez wykonywanie cały czas tych samych partii kodu. W przypadku nauki języków wyższego poziomu bardzo często zabrania się stosowania skoków lub nawet nie implementuje się ich w składni języka. Oczywistym jest, że każdy język korzysta ze skoków mimo iż jawnie nie oferuje ich użycia programistom. W asemblerze możemy się posługiwać skokami do woli i stanowią one dość znaczącą część kodu każdego programu. Skoki stosuje się gdy pewna część kodu ma być wykonana przy spełnieniu określonego warunku. Co charakterystyczne dla procesorów warunek ten jest postaci działania arytmetycznego lub logicznego na liczbach binarnych. Skoki warunkowe opierają się w swoim działaniu na stanie rejestru flag. Stany poszczególnych jego bitów określają bezpośrednio czy dany skok ma być wykonany czy też nie. Pierwszym prostym przykładem może być bit określający zerowy wynik czyli bit ZF (Zero Flag). Bit ten ustawiany jest jeżeli wynik ostatniej instrukcji obliczeniowej wynosił 0. Oczywiście w innym wypadku stan tego bitu ma wartość 0. Przykład 1:
label: mov ah, 5 sub ah, 5 jz label Jest to jednocześnie przykład błędnie napisanego programu ponieważ wpędzi on procesor w nieskończoną pętlę, ale nie to jest tutaj najważniejsze. Na początku wpisujemy do rejestru ah wartość 5, a następnie odejmujemy wartość 5 co w wyniku da nam zerową wartość rejestru ah. Jednocześnie po wykonaniu drugiej instrukcji procesor automatycznie ustawi flagę ZF w rejestrze flag. Dzięki temu instrukcja skoku znajdująca się w kolejnej linii wykona skok programu do etykiety label, czyli program wykonał by się od początku co prowadzi jak wcześniej wspomniałem do nieskończonej pętli. Teraz przedstawię trochę poprawiony kod. Przykład 2:
... mov al, 14 sub ah, 5 jz label mov al, 15 label: ... Załóżmy, że ten kod jest częścią jakiegoś programu. W tym programie wykonywany jest również warunkowy skok oparty na fladze ZF. W tym przypadku nie mamy podanej jawnie początkowej wartości rejestru ah. Jeżeli ta wartość wynosiła 5 to po odjęciu od niej 5 wyzerujemy rejestr co w wyniku da nam wykonanie przez procesor skoku do etykiety label. W wyniku oczywiście nie zostanie wykonana instrukcja wpisująca do rejestru al wartości 15 i zostanie tam poprzednio wpisana wartość 14. Jeżeli wartość rejestru ah na początku miała by jakąkolwiek inną wartość niż 5 to otrzymamy w rejestrze al wartość 15. Jest to najprostszy przykład kodu wykonywanego warunkowo. Instrukcje skoku warunkowego działają na różnych flagach rejestru flag i czasami nawet na kilku jednocześnie. Programista nie musi praktycznie wiedzieć na jakich flagach procesor wykonuje testowanie wartości ponieważ często najistotniejszym jest relacja jaka wynika z porównywania liczb. Można oczywiście porównywać liczby po przez odejmowanie i sprawdzanie np. stanu flagi ZF jak to opisałem powyżej jednak nie jest to najlepsza instrukcja. Do porównywania została wydzielona oddzielna instrukcja o nazwie cmp (compare) działająca na dwóch argumentach. Asembler dodatkowo serwuje nam szereg skoków warunkowych wykonujących skok jeżeli spełniona jest odpowiednia relacja między argumentami instrukcji cmp. Trzeba pamiętać iż praktycznie każda instrukcja asemblera zmienia flagi procesora dlatego porównując liczby powinniśmy używać instrukcji cmp tuż przed odpowiadającymi im instrukcjami skoków warunkowych. Przykład 3:
... mov al, 14 cmp ah, 5 jz label mov al, 15 label: ... Kod z przykładu 3 działał by identycznie jak poprzedni z tą zaletą iż przy sprawdzaniu wartości rejestru ah nie zmienia jego wartości. Dzięki temu można na przykład wykonać na nim kolejne porównanie z innymi wartościami. Instrukcja cmp jako argumenty może przyjmować rejestr-rejestr, rejestr-bezpośrednia wartość, rejestr-pamięć lub pamięć-bezpośrednia wartość. Instrukcje skoku natomiast mogą przyjmować dużo bardziej skompilowane testy na rejestrze flag niż tylko test zera. To oczywiście owocuje całą masą różnych form skoków warunkowych, które wypisane są w tabeli poniżej. Składnia instrukcji skoku warunkowego przedstawia się następująco: j + [negacja] + [warunek nierówności] + [warunek równania] {etykieta}
Nie wszystkie człony składające się na nazwę instrukcji muszą w niej wystąpić. W przypadku sprawdzania czy oba argumenty są sobie równe instrukcja przyjmuje w nazwie tylko literkę j + warunek równości co daje w wyniku instrukcję: je label W rzeczywistości jest to ta sama instrukcja, którą przedstawiłem wcześniej testująca stan flagi ZF. Różnica polega na tym, że przedstawiona w tej formie bardziej intuicyjnie informuje nazwą swoje działanie. Można ją mianowicie rozszyfrować jako je = jump if eual (skocz jeżeli równe). je należy do pewnego zestawu instrukcji, które swoimi skrótowymi nazwami określają relację między argumentami. Cały zestaw tych instrukcji znajduje się poniżej.
W przypadku nierówności argumentów rozróżnia się oddzielne instrukcje dla liczb ze znakiem i bez znaku. Użycie instrukcji cmp powoduje sprawdzenie relacji pierwszego argumentu względem drugiego. Przykład 4:
... cmp ax, bx jae label ... W tym kodzie skok do instrukcji label zostanie wykonany jeżeli ax >= bx. org 0x100 cmp ax, bx ja wieksze cmp ax, bx jb mniejsze mov dx, msg_rowne mov ah, 9 int 0x21 mov ax, 0x4C00 int 0x21 wieksze: mov dx, msg_wieksze mov ah, 9 int 0x21 mov ax, 0x4C00 int 0x21 mniejsze: mov dx, msg_mniejsze mov ah, 9 int 0x21 mov ax, 0x4C00 int 0x21 msg_wieksze db "ax > bx$" msg_mniejsze db "ax < bx$" msg_rowne db "ax = bx$" Powyższy program sprawdzi jaka jest relacja między wartościami rejestrów ax i bx i wypisze na ekran odpowiednią informację. Program oczywiście sam w sobie nie ma większego sensu i ma na celu pokazanie jak wykonuje się warunkowo pewne partie kodu. Jeżeli chodzi o bardziej sensowne wykorzystanie możemy napisać program, który czeka aż użytkownik naciśnie klawisz, a następnie wypiszę na ekran informację czy wciśniętym klawiszem był klawisz "r". Do tego oczywiście będzie potrzebna kolejna procedura systemowa pobierająca klawisz z urządzenia klawiatury. Jest to funkcja 7 przerwania 0x21 i zwraca ona w rejestrze al kod ASCII znaku "r". org 0x100 mov ah, 7 int 0x21 mov dx, msg_no cmp al, 'r' jnz no_r mov dx, msg_yes no_r: mov ah, 9 int 0x21 mov ax, 0x4C00 int 0x21 msg_no db "Wcisnieto inny klawisz niz 'r'$" msg_yes db "Wcisnieto klawisz 'r'$" Poza porównywaniem liczb skoki warunkowe mogą być wykonywane bezpośrednio testując jedną z flag. Mogą to być flagi CF, SF, OF i PF. Instrukcje im odpowiadające to:
Te instrukcje nie są zbyt często stosowane ponieważ dotyczą dość niepraktycznych warunków w odniesieniu do porównywania liczb. Z instrukcji testujących pojedyncze flagi najczęściej stosuje się jz/jnz (działanie identyczne jak je/jne) i jc/jnc. Flaga CF bardzo często zostaje użyta do informowania programu czy poprzednio wywołana procedura lub przerwanie wykonało się prawidłowo. Przyjęła się konwencja ustawiania flagi CF jeżeli podprogram lub przerwanie zakończyło swoje działanie z błędem, a zerowanie CF jeżeli zakończyło się prawidłowym wynikiem.
|