Skok bezwarunkowy
Kod wykonującego się programu umieszczony jest w spójnym bloku pamięci operacyjnej. Adres (czyli numer) komórki zawierającej
aktualnie wykonywaną instrukcję znajduje się w rejestrze ip (ang. Instruction Pointer). Jeśli bieżący rozkaz nie zmienił wartości ip,
to jest ona zwiększana automatycznie tak, by wskazywać na następną instrukcję. Rejestr ip nie jest poprawnym argumentem dla poznanych dotychczas instrukcji
mov, add, sub. Do jego zmiany służą specjalne rozkazy, z których najprostszym jest rozkaz skoku
bezwarunkowego jmp.
1
2
3
| asm("start:");
printf("Nie lubię się powtarzać.");
asm("jmp start"); |
Tłumacząc ten fragment kodu kompilator zapamięta adres pierwszej
instrukcji po etykiecie start i wstawi go jako operand instrukcji
jmp. Gdy uruchomimy program, funkcja
printf wykonywać się będzie w nieskończoność. Zobaczyliśmy tu przykład
skoku wstecz, czyli do wcześniejszego miejsca w kodzie. Można też
skakać wprzód, czyli do etykiety zdefiniowanej później, niż jej występuje jej użycie. Rozkaz
jmp
sam w sobie jest mało przydatny. Chcielibyśmy uzależnić wykonanie skoku np. od wyniku operacji arytmetycznej.
Flagi i testowanie flag
Instrukcje arytmetyczne zmieniają wartość nie tylko rejestru/adresu będącego argumentem docelowym. Powodują też zmianę wartości specjalnych bitów ( czyli flag lub znaczników ) w rejestrze stanu procesora. Jeżeli wynikiem ostatniej operacji
arytmetycznej było zero to ustawiony zostanie bit ZF (ang Zero Flag) tego rejestru, w przeciwnym przypadku będzie on wyzerowany. Jeśli wynikiem była liczba mniejsza od zera to ustawiony będzie bit SF (ang. Sign Flag) a jeśli większa bądź równa zeru to wyzerowany. Bity ZF i SF są to odpowiednio bity 6 i 7 rejestru stanu, któy nazywa się flags.
Skoki warunkowe
Przy pomocy poznanego właśnie rejestru
flags możemy wykonywać skoki warunkowe.
Poniższy program przypisuje zmiennej
max maksimum z wartości zmiennych
i
i
j:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| int i,j, max;
int main(void)
{
i = 7; j = 9;
asm(" mov eax, i; \
mov ebx, eax; \
mov ecx, j; \
sub eax, ecx; \
jg end; \
mov ebx, ecx; \
end: \
mov max, ebx; \
");
printf("%i\n", max); //max==9
return 0;
} |
W wierszu 8 wykonywane jest odejmowanie
i-j. W zależności od znaku wyniku tego działania ustawiana bądź zerowana jest flaga
SF rejestru
flags. Instukcja
jg end; wykona skok do etykiety
end jeżeli ta flaga jest wyzerowana, tzn. wtedy gdy wynik działania
i-j był dodatni.
Inne przydatne rozkazy skoków:
- (ang. jump if equal) - skocz gdy wynikiem operacji arytmetycznej było zero (znacznik
ZF jest ustawiony).
- (ang. jump if greater or equal) - skocz gdy wynik był większy bądź równy zeru ( znacznik
SF wyzerowany)
- (ang. jump if less than)
- skocz gdy wynik był mniejszy od zera(
SF ustawiony)
- (ang. jump if less or equal) - skocz gdy wynik był mniejszy bądź równy zeru(
ZF ustawiony lub SF ustawiony)
- (ang. jump if not equal) - skocz gdy wynik był różny od zera (
ZF wyzerowany)
Powyższe rozkazy nie zmieniają rejestru
flags.
Zauważmy, że zmiana wartości rejestru
eax instrukcją z wiersza 8 nie jest przez nigdzie wykorzystywana. Interesowało nas jedynie ustawienie flag.
Nawet więcej, gdybyśmy nie zniszczyli wartości tego rejestru
to moglibyśmy użyć go zamiast
ebx. Takie sytuacje są na tyle częste, że wprowadzono rozkaz
który wykonuje operację
operand1-operand2 ale nie zapisuje nigdzie wyniku. Ustawia jedynie rejestr flag. Uproszczony kod wygląda tak:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| int i,j, max;
int main(void)
{
i = 7; j = 9;
asm(" mov eax, i; \
mov ecx, j; \
cmp eax, ecx; \
jg end; \
mov eax, ecx; \
end: \
mov max, eax; \
");
printf("%i\n", max); //max==9
return 0;
} |
|
Ćwiczenie 3. Niech i,j,k będą zmiennymi typu short int. Napisz wstawkę asemblerową
podstawiającą do zmiennej k wartość 0 jeżeli i<j, wartość 1 jeżeli i==j, wartość 2 jeżeli i>j.
|
|
|
Pętle
Zobaczmy teraz przykład wstawki z pętlą. Poniższy program
wykona zapisze w
k wartość
i*j obliczoną w dosyć naiwny sposób - dodając
j razy liczbę
i do
ax po czym umieszczając wartość
ax w
k.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| short int i,j, k;
int main(void)
{
i = 7; j = 9;
asm(" mov ax,0; \
mov cx,j; \
start: \
cmp cx,0; \
je end; \
add ax,i; \
sub cx,1; \
jmp start; \
end: \
mov k, ax; \
");
printf("%i\n", k); //k==63
return 0;
} |
W rejestrze
ax obliczana jest żądana suma. Rejestr
cx ustawiony początkowo na
j służy jako licznik pętli. Program wykona
j dodawań zmiennej
i do rejestru
ax.
Wśród programistów asemblerowych przyjął się zwyczaj używania
rejestru
(e)cx jako licznika w rozmaitych pętlach.
Często zachodzi potrzeba, jak w naszym przykładzie, wykonania skoku gdy
(e)cx jest równy zeru. W nowszych procesorach wprowadzono odpowiednie ku temu instrukcje:
- (Jump if
cx register is zero) skocz gdy cx jest wyzerowany
- (Jump if
ecx register is zero) skocz gdy ecx jest wyzerowany
Rozkazy te nie używają rejestru
flags.
W wierszu 10 widzimy instrukcję
add ax,i. Wykonuje
się ona w pętli
j razy.
Ponieważ wartość
i nie ulega modyfikacji, to powinno się składować ją w rejestrze. Zamiast
sub cx,1 można użyć instrukcji dekrementacji
dec cx. Dla porządku trzeba dodać, że również
zamiast
add rejestr,1 zwykle lepiej jest napisać
inc rejestr. Oto poprawiony kod:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| short int i,j, k;
int main(void)
{
i = 7; j = 9;
asm(" mov ax,0; \
mov bx,i; \
mov cx,j; \
start: \
jcxz end; \
add ax,bx; \
dec cx; \
jmp start; \
end: \
mov k, ax; \
");
printf("%i\n", k); //k==63
return 0;
} |
Poniższy program, dla
N>0, wypisuje na ekranie wartość
N-tej
liczby Fibonacciego:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| const unsigned short N = ?;
unsigned short i,j;
int main(void)
{
unsigned short k = 1;
unsigned short tmp;
i = 0; j = 1;
//----------------------------
while (k++ < N ) {
tmp = j;
j += i;
i = tmp;
}
//----------------------------
printf("%i\n", j);
return 0;
} |