BLOK 2: Pętle, tablice i łańcuchy

Informacje organizacyjne

Termin oddania: do 02.01.2026
Punktacja: 8 punktów
Opóźnienie: -3 punkty za każdy tydzień


1. Środowisko pracy

Kompilacja w DOSBox-X

1
2
D:\> cd project
D:\PROJECT> cl program

Skrypt cl automatycznie wykonuje:

  1. tasm program.asm - kompilacja do .OBJ
  2. tlink program.obj - linkowanie do .EXE
  3. Wyświetla ewentualne błędy

Debugowanie - TurboDebugger

1
D:\PROJECT> td program.exe

Podstawowe komendy:

  • F7 - Step Into (krok do instrukcji)
  • F8 - Step Over (pomiń CALL)
  • F9 - Run to cursor
  • Ctrl+F2 - Reset programu
  • Alt+V - View menu (rejestry, pamięć, stos)

Porównanie z Visual Studio

Na zajęciach pokażę jak ten sam kod C wygląda w asemblerze pod Windows (x64).
To pomoże zrozumieć koncepcje uniwersalne dla każdej architektury.


2. Instrukcje asemblera - rozszerzenie wiedzy

2.1. Pętle w asemblerze

Instrukcja LOOP

1
2
3
4
mov cx, 10        ; Ustaw licznik na 10
petla:
    ; Kod do powtórzenia
    loop petla    ; CX--, jeśli CX ≠ 0 skocz do 'petla'

Jak działa LOOP:

  1. Dekrementuje CX (CX = CX - 1)
  2. Sprawdza czy CX ≠ 0
  3. Jeśli tak - skacze do etykiety
  4. Jeśli CX = 0 - kontynuuje dalej

UWAGA: LOOP zawsze dekrementuje CX, nawet jeśli CX=0!
Wtedy CX staje się FFFFh (65535) → pętla wykona się 65535 razy!

Alternatywa: pętle z porównaniem

1
2
3
4
5
6
mov cx, 0         ; Licznik od 0
petla:
    ; Kod
    inc cx
    cmp cx, 10
    jl petla      ; Jump if Less (cx < 10)

Porównanie:

MetodaZaletyWady
LOOPKrótki kod, klasycznyTylko malejący licznik, ryzyko przy CX=0
CMP+JMPDowolny kierunek, elastycznyDłuższy kod

2.2. Operacje na stringach (łańcuchach)

Procesor ma specjalne instrukcje do pracy ze stringami. Używają rejestrów SI (source index) i DI (destination index).

Flaga kierunku (DF)

1
2
cld    ; Clear Direction Flag - SI/DI rosną (w górę pamięci)
std    ; Set Direction Flag - SI/DI maleją (w dół pamięci)

ZAWSZE ustawiaj cld na początku operacji stringowych!

LODSB/LODSW - Load String

1
2
3
4
5
; LODSB - Load String Byte
lodsb    ; AL = [DS:SI], następnie SI++

; LODSW - Load String Word
lodsw    ; AX = [DS:SI], następnie SI+=2

STOSB/STOSW - Store String

1
2
3
4
5
; STOSB - Store String Byte
stosb    ; [ES:DI] = AL, następnie DI++

; STOSW - Store String Word
stosw    ; [ES:DI] = AX, następnie DI+=2

MOVSB/MOVSW - Move String

1
2
3
4
5
; MOVSB - Move String Byte
movsb    ; [ES:DI] = [DS:SI], SI++, DI++

; MOVSW - Move String Word
movsw    ; [ES:DI] = [DS:SI], SI+=2, DI+=2

CMPSB/CMPSW - Compare String

1
2
3
4
5
; CMPSB - Compare String Byte
cmpsb    ; Porównuje [DS:SI] z [ES:DI], SI++, DI++, ustawia flagi

; CMPSW - Compare String Word
cmpsw    ; Porównuje słowa, SI+=2, DI+=2

SCASB/SCASW - Scan String

1
2
3
4
5
; SCASB - Scan String Byte
scasb    ; Porównuje AL z [ES:DI], DI++, ustawia flagi

; SCASW - Scan String Word
scasw    ; Porównuje AX z [ES:DI], DI+=2

Prefiksy powtarzania

1
2
3
rep movsb      ; Powtarzaj MOVSB dopóki CX ≠ 0 (CX razy)
repz cmpsb     ; Powtarzaj dopóki ZF=1 i CX≠0 (dopóki równe)
repnz scasb    ; Powtarzaj dopóki ZF=0 i CX≠0 (dopóki różne)

2.3. Adresowanie tablic

Podstawowe tryby

1
2
3
mov al, [bx]       ; AL = bajt pod adresem DS:BX
mov ax, [si]       ; AX = słowo pod adresem DS:SI
mov bl, [di]       ; BL = bajt pod adresem DS:DI (lub ES:DI dla stringów)

Displacement (przesunięcie)

1
2
mov al, [bx+5]     ; AL = bajt pod adresem DS:BX+5
mov ax, [si+10]    ; AX = słowo pod adresem DS:SI+10

Indeksowanie złożone

1
2
mov al, [bx+si]    ; AL = bajt pod adresem DS:BX+SI
mov ax, [bp+di]    ; AX = słowo pod adresem SS:BP+DI

Przesunięcie + indeks

1
mov al, [bx+si+5]  ; AL = bajt pod adresem DS:BX+SI+5

2.4. Różnica: bajt vs słowo w tablicach

KRYTYCZNE DO ZROZUMIENIA:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
.data
    tablica_b db 10, 20, 30, 40, 50    ; Tablica bajtów
    tablica_w dw 10, 20, 30, 40, 50    ; Tablica słów (dwa bajty)

.code
    ; Dostęp do tablicy bajtów
    mov si, 0
    mov al, tablica_b[si]    ; AL = 10
    inc si                   ; SI = 1
    mov al, tablica_b[si]    ; AL = 20
    
    ; Dostęp do tablicy słów
    mov si, 0
    mov ax, tablica_w[si]    ; AX = 10
    add si, 2                ; SI = 2 (słowo = 2 bajty!)
    mov ax, tablica_w[si]    ; AX = 20

Częsty błąd: Inkrementacja inc si dla tablicy słów - to da dostęp do środka słowa!

1
2
3
4
5
; BŁĄD:
mov si, 0
mov ax, tablica_w[si]    ; AX = 10 (00 0A w pamięci little-endian)
inc si                   ; SI = 1 - wskazuje ŚRODEK słowa!
mov ax, tablica_w[si]    ; AX = nieprawidłowa wartość

3. Przykłady: C vs Asembler

Przykład 1: Pętla for - zliczanie

Kod C:

1
2
3
4
5
int count = 0;
for (int i = 0; i < 10; i++) {
    count++;
}
printf("Count: %d\n", count);

Asembler x86-16 (TASM):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.model small
.stack 256
.data
    wynik dw 0

.code
main proc
    mov ax, @data
    mov ds, ax
    
    mov cx, 10        ; i = 10 (liczymy w dół)
    mov ax, 0         ; count = 0
    
petla:
    inc ax            ; count++
    loop petla        ; i--, if i != 0 goto petla
    
    mov wynik, ax     ; Zapisz wynik
    
    ; Zakończenie
    mov ax, 4C00h
    int 21h
main endp
end main

Visual Studio (x64 - do demonstracji):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
; Visual Studio - Disassembly view
    mov dword ptr [count], 0
    mov dword ptr [i], 0
    jmp short check_loop
loop_body:
    mov eax, dword ptr [count]
    inc eax
    mov dword ptr [count], eax
    mov eax, dword ptr [i]
    inc eax
    mov dword ptr [i], eax
check_loop:
    cmp dword ptr [i], 10
    jl short loop_body

Analiza różnic:

  • DOS: Używamy CX i instrukcji LOOP - procesor to rozumie natywnie
  • x64: Kompilator używa jawnych porównań CMP i skoków JL
  • DOS: 16-bit rejestry (AX, CX)
  • x64: 32/64-bit rejestry (EAX, RAX)

Przykład 2: Tablica - suma elementów

Kod C:

1
2
3
4
5
6
7
8
int tablica[5] = {10, 20, 30, 40, 50};
int suma = 0;

for (int i = 0; i < 5; i++) {
    suma += tablica[i];
}

printf("Suma: %d\n", suma);

Asembler (TASM) - wersja 1 z LOOP:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
.model small
.stack 256
.data
    tablica dw 10, 20, 30, 40, 50    ; 5 słów
    suma dw 0

.code
main proc
    mov ax, @data
    mov ds, ax
    
    mov cx, 5              ; Licznik elementów
    mov si, 0              ; Indeks (w bajtach)
    mov ax, 0              ; Akumulator sumy
    
petla:
    add ax, tablica[si]    ; suma += tablica[i]
    add si, 2              ; Następny element (słowo = 2 bajty)
    loop petla
    
    mov suma, ax           ; Zapisz wynik
    
    mov ax, 4C00h
    int 21h
main endp
end main

Asembler (TASM) - wersja 2 z LODSW:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
.model small
.stack 256
.data
    tablica dw 10, 20, 30, 40, 50
    suma dw 0

.code
main proc
    mov ax, @data
    mov ds, ax
    
    cld                    ; Kierunek w górę
    lea si, tablica        ; SI wskazuje na tablicę
    mov cx, 5              ; 5 elementów
    mov bx, 0              ; Akumulator sumy
    
petla:
    lodsw                  ; AX = [DS:SI], SI += 2
    add bx, ax             ; suma += AX
    loop petla
    
    mov suma, bx
    
    mov ax, 4C00h
    int 21h
main endp
end main

Analiza:

  • Wersja 1: Jawne indeksowanie tablica[si]
  • Wersja 2: Użycie instrukcji stringowej LODSW - procesor automatycznie przesuwa SI
  • Oba podejścia są poprawne, LODSW jest bardziej “idiomatic” dla x86

Przykład 3: String - długość łańcucha

Kod C:

1
2
3
4
5
6
7
8
char tekst[] = "Hello";
int dlugosc = 0;

while (tekst[dlugosc] != '\0') {
    dlugosc++;
}

printf("Długość: %d\n", dlugosc);

Asembler (TASM) - wersja 1 ręczna:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
.model small
.stack 256
.data
    tekst db 'Hello', 0    ; String zakończony zerem
    dlugosc dw 0

.code
main proc
    mov ax, @data
    mov ds, ax
    
    mov si, 0              ; Indeks
    
petla:
    mov al, tekst[si]      ; Pobierz bajt
    cmp al, 0              ; Czy koniec?
    je koniec              ; Jeśli tak - zakończ
    inc si                 ; dlugosc++
    jmp petla
    
koniec:
    mov dlugosc, si        ; Zapisz długość
    
    mov ax, 4C00h
    int 21h
main endp
end main

Asembler (TASM) - wersja 2 z SCASB:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
.model small
.stack 256
.data
    tekst db 'Hello', 0
    dlugosc dw 0

.code
main proc
    mov ax, @data
    mov ds, ax
    mov es, ax             ; ES = DS (SCASB używa ES:DI)
    
    cld                    ; Kierunek w górę
    lea di, tekst          ; DI wskazuje na tekst
    mov al, 0              ; Szukamy zera
    mov cx, 0FFFFh         ; Maksymalna długość
    
    repnz scasb            ; Szukaj AL w [ES:DI], aż znajdziesz (ZF=1)
    
    ; CX teraz zawiera: FFFFh - długość - 1
    ; Dlatego długość = FFFFh - CX
    neg cx                 ; CX = -CX
    dec cx                 ; CX = -(FFFFh - długość - 1) - 1 = długość
    
    mov dlugosc, cx
    
    mov ax, 4C00h
    int 21h
main endp
end main

Analiza:

  • Wersja 1: Prosta, zrozumiała, jawna logika
  • Wersja 2: Używa specjalizowanej instrukcji SCASB z prefiksem REPNZ
    • REPNZ = Repeat while Not Zero (dopóki nie znajdzie)
    • Po zakończeniu CX zawiera liczbę pozostałych iteracji
    • Matematyka: długość = max_cx - pozostałe_cx - 1

Przykład 4: Debugowanie w TurboDebugger

Program do zademonstrowania:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.model small
.stack 256
.data
    tablica db 1, 2, 3, 4, 5

.code
main proc
    mov ax, @data
    mov ds, ax
    
    mov cx, 5
    mov si, 0
    mov bl, 0
    
petla:
    mov al, tablica[si]    ; Breakpoint tutaj (F9)
    add bl, al
    inc si
    loop petla
    
    mov ax, 4C00h
    int 21h
main endp
end main

Co pokazać studentom w TD:

  1. View → Registers - obserwacja CX, SI, BL w czasie rzeczywistym
  2. View → Dump - zawartość pamięci, tablica w formacie heksadecymalnym
  3. View → Stack - jak wygląda stos programu
  4. F7 (Step) - wykonanie instrukcji krok po kroku
  5. Śledzenie jak:
    • CX maleje z każdym LOOP
    • SI rośnie co iterację
    • BL akumuluje sumę
    • Flagi ZF, CF się zmieniają

4. Zadania do wykonania

Forma oddawania

Każde zadanie oddajemy jako:

  1. Kod źródłowy (.asm)
  2. Skompilowany program (.exe)
  3. Sprawozdanie (PDF lub MD) zawierające:
    • Opis znalezionego błędu
    • Wyjaśnienie DLACZEGO jest to błąd (z perspektywy architektury)
    • Poprawiony kod
    • Wynik działania programu

Zadanie 6: Zliczanie znaków (kod z błędem)

Treść zadania: Poniższy program ma wczytywać znaki z klawiatury aż do naciśnięcia Enter (kod 0Dh),
a następnie wyświetlić liczbę wprowadzonych znaków (bez liczenia samego Enter).

Kod z błędem:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
.model small
.stack 256
.data
    komunikat db 'Wprowadz tekst: $'
    wynik db 0Dh, 0Ah, 'Liczba znakow: $'

.code
main proc
    mov ax, @data
    mov ds, ax
    
    ; Wyświetl komunikat
    lea dx, komunikat
    mov ah, 09h
    int 21h
    
    mov cx, 0              ; Licznik znaków
    
wczytuj:
    mov ah, 01h            ; Funkcja 01h - czytaj znak z echo
    int 21h
    
    inc cx                 ; Zwiększ licznik
    
    cmp al, 0Ah            ; Czy to Enter?
    jne wczytuj            ; Jeśli nie - czytaj dalej
    
    ; Wyświetl wynik
    lea dx, wynik
    mov ah, 09h
    int 21h
    
    ; Wyświetl liczbę (zakładamy < 10)
    mov ax, cx
    add al, '0'
    mov dl, al
    mov ah, 02h
    int 21h
    
    mov ax, 4C00h
    int 21h
main endp
end main

Twoje zadanie:

  1. Uruchom program i przetestuj
  2. Znajdź błąd (jest ich kilka - znajdź główny)
  3. Wyjaśnij:
    • Co program robi źle?
    • Jaki kod zwraca klawisz Enter w DOS? (sprawdź w skrypcie lub dokumentacji)
    • Dlaczego licznik jest niepoprawny?
  4. Popraw kod i dołącz wynik działania

Podpowiedź: Sprawdź co zwraca INT 21h/AH=01h po naciśnięciu Enter w DOS.


Zadanie 7: Odwracanie ciągu znaków (kod z błędem)

Treść zadania: Program ma wczytać ciąg znaków (max 20 znaków) zakończony Enterem,
a następnie wyświetlić go w odwrotnej kolejności.

Kod z błędem:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
.model small
.stack 256
.data
    bufor db 21 dup(0)        ; Bufor na 20 znaków + Enter
    komunikat db 'Wpisz tekst (max 20 znakow): $'
    wynik db 0Dh, 0Ah, 'Odwrocony tekst: $'

.code
main proc
    mov ax, @data
    mov ds, ax
    
    ; Wyświetl komunikat
    lea dx, komunikat
    mov ah, 09h
    int 21h
    
    ; Wczytaj znaki do bufora
    mov si, 0
    
wczytuj:
    mov ah, 01h
    int 21h
    
    cmp al, 0Dh               ; Enter?
    je zakoncz_wczytywanie
    
    mov bufor[si], al
    inc si
    cmp si, 20                ; Max 20 znaków
    jl wczytuj
    
zakoncz_wczytywanie:
    ; SI zawiera długość
    ; Teraz wyświetl w odwrotnej kolejności
    
    lea dx, wynik
    mov ah, 09h
    int 21h
    
    ; Wyświetl od końca
    mov cx, si                ; CX = długość
    
wyswietl:
    dec si
    mov dl, bufor[si]
    mov ah, 02h
    int 21h
    loop wyswietl
    
    mov ax, 4C00h
    int 21h
main endp
end main

Twoje zadanie:

  1. Uruchom program i przetestuj z różnymi długościami tekstu
  2. Znajdź błąd związany z krótkim tekstem (np. 1 znak)
  3. Wyjaśnij:
    • Co się dzieje gdy wpiszesz tylko 1 znak?
    • Dlaczego program wyświetla nieprawidłowe znaki?
    • Jaki jest stan SI po dec si gdy SI=0?
  4. Popraw kod

Podpowiedź: Co się stanie gdy wykonasz dec si a SI już jest zerem?
Jak to wpływa na bufor[si] w kontekście 16-bitowego adresowania?


Zadanie 8: Tablica liczb - suma (kod z błędem)

Treść zadania: Program ma obliczyć sumę elementów tablicy 5 liczb i wyświetlić wynik.

Kod z błędem:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
.model small
.stack 256
.data
    tablica dw 100, 200, 50, 75, 25     ; Suma = 450
    suma dw 0
    komunikat db 'Suma elementow tablicy: $'

.code
main proc
    mov ax, @data
    mov ds, ax
    
    ; Oblicz sumę
    mov cx, 5              ; 5 elementów
    mov si, 0              ; Indeks
    
petla:
    mov ax, tablica[si]
    add suma, ax
    inc si                 ; Następny element
    loop petla
    
    ; Wyświetl komunikat
    lea dx, komunikat
    mov ah, 09h
    int 21h
    
    ; Wyświetl sumę (zakładamy < 1000, 3 cyfry)
    mov ax, suma
    
    ; Setki
    mov bl, 100
    div bl                 ; AL = setki, AH = reszta
    add al, '0'
    mov dl, al
    mov ah, 02h
    int 21h
    
    ; Dziesiątki i jedności - dalszy kod...
    ; (uproszczone dla zadania)
    
    mov ax, 4C00h
    int 21h
main endp
end main

Twoje zadanie:

  1. Uruchom program
  2. Program prawdopodobnie się zawiesi lub da błędny wynik - dlaczego?
  3. Znajdź błąd w pętli
  4. Wyjaśnij:
    • Jak duży jest element typu dw (word)?
    • O ile bajtów powinniśmy przesuwać SI dla tablicy słów?
    • Co się stanie gdy robimy inc si dla tablicy dw?
    • Gdzie w pamięci SI będzie wskazywać po pierwszym inc si?
  5. Popraw kod

Podpowiedź: Tablica dw = tablica słów. Słowo = 2 bajty.
Jak to wpływa na przesuwanie indeksu?


Zadanie 9: Znajdź maksimum (kod z błędem)

Treść zadania: Program ma znaleźć wartość maksymalną w tablicy 10 liczb bez znaku (unsigned).

Kod z błędem:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
.model small
.stack 256
.data
    ; Tablica liczb bez znaku (unsigned byte)
    tablica db 45, 120, 33, 255, 67, 200, 15, 89, 178, 50
    maksimum db 0
    komunikat db 'Maksimum: $'

.code
main proc
    mov ax, @data
    mov ds, ax
    
    ; Znajdź maksimum
    mov cx, 10             ; 10 elementów
    mov si, 0
    mov al, 0              ; Aktualne maksimum
    
petla:
    mov bl, tablica[si]
    cmp bl, al
    jl nie_wieksze         ; Jump if Less (bl < al)
    mov al, bl             ; Nowe maksimum
    
nie_wieksze:
    inc si
    loop petla
    
    mov maksimum, al
    
    ; Wyświetl komunikat
    lea dx, komunikat
    mov ah, 09h
    int 21h
    
    ; Wyświetl wartość (uproszczone)
    mov dl, al
    add dl, '0'
    mov ah, 02h
    int 21h
    
    mov ax, 4C00h
    int 21h
main endp
end main

Twoje zadanie:

  1. Uruchom program i sprawdź wynik
  2. Program nie znajduje prawidłowego maksimum - dlaczego?
  3. Znajdź błąd koncepcyjny
  4. Wyjaśnij:
    • Z jaką wartością inicjalizujemy AL?
    • Co się stanie jeśli wszystkie liczby w tablicy są większe od 0?
    • Jaka liczba jest największa w tablicy?
    • Dlaczego algorytm nie znajduje tej liczby?
  5. Wyjaśnij różnicę między instrukcjami:
    • JL (Jump if Less)
    • JG (Jump if Greater)
    • Która jest poprawna dla naszego przypadku?
  6. Popraw kod

Podpowiedź: Pomyśl jak powinno wyglądać prawidłowe zainicjalizowanie maksimum.
Czy inicjalizacja na 0 ma sens dla algorytmu znajdowania maksimum?


Zadanie 10: Sortowanie bąbelkowe (kod z błędem)

Treść zadania: Program ma posortować rosnąco tablicę 8 liczb algorytmem sortowania bąbelkowego
i wyświetlić posortowaną tablicę.

Kod z błędem:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
.model small
.stack 256
.data
    tablica db 64, 34, 25, 12, 22, 11, 90, 88
    n dw 8
    komunikat db 'Posortowana tablica: $'

.code
main proc
    mov ax, @data
    mov ds, ax
    
    ; Sortowanie bąbelkowe
    ; Zewnętrzna pętla (i = 0 to n-1)
    mov cx, n
    dec cx                 ; CX = n-1 (liczba przejść)
    
petla_zewnetrzna:
    push cx                ; Zapisz licznik zewnętrzny
    
    ; Wewnętrzna pętla (j = 0 to n-i-1)
    mov bx, 0              ; BX = indeks j
    
petla_wewnetrzna:
    ; Porównaj tablica[j] z tablica[j+1]
    mov al, tablica[bx]
    mov dl, tablica[bx+1]
    
    cmp al, dl
    jle nie_zamieniaj      ; Jump if Less or Equal (al <= dl)
    
    ; Zamień miejscami
    mov tablica[bx], dl
    mov tablica[bx+1], al
    
nie_zamieniaj:
    inc bx
    cmp bx, cx             ; j < n-1-i?
    jl petla_wewnetrzna
    
    pop cx                 ; Przywróć licznik zewnętrzny
    loop petla_zewnetrzna
    
    ; Wyświetl posortowaną tablicę
    lea dx, komunikat
    mov ah, 09h
    int 21h
    
    mov cx, 8
    mov si, 0
    
wyswietl:
    mov dl, tablica[si]
    add dl, '0'            ; Konwersja na ASCII (dla cyfr < 10)
    mov ah, 02h
    int 21h
    
    mov dl, ' '            ; Spacja
    mov ah, 02h
    int 21h
    
    inc si
    loop wyswietl
    
    mov ax, 4C00h
    int 21h
main endp
end main

Twoje zadanie:

  1. Uruchom program i sprawdź wynik
  2. Program nie sortuje poprawnie - dlaczego?
  3. Znajdź błąd w warunku pętli wewnętrznej
  4. Wyjaśnij:
    • Algorytm bąbelkowy wymaga: każdy element porównać z następnym
    • Ile razy powinna wykonać się pętla wewnętrzna w pierwszym przejściu?
    • Jaka wartość jest w CX podczas pętli wewnętrznej?
    • Czy porównanie cmp bx, cx jest poprawne?
    • Co powinno być prawidłowym warunkiem dla pętli wewnętrznej?
  5. Dodatkowe pytanie: Dlaczego używamy jle zamiast jge?
  6. Popraw kod i załącz prawidłowo posortowaną tablicę

Podpowiedź: Pętla wewnętrzna powinna wykonać się n-1 razy w pierwszym przejściu,
n-2 razy w drugim itd. Czy aktualny kod to realizuje?
Przeanalizuj wartość CX w kontekście obu pętli.


5. Wskazówki ogólne

Dobre praktyki

  1. Zawsze inicjalizuj rejestry

    1
    
    mov cx, 0    ; Jawna inicjalizacja
    
  2. Sprawdzaj rozmiary danych

    1
    2
    
    db = 1 bajt, inc si o 1
    dw = 2 bajty, inc si o 2 (lub add si, 2)
    
  3. Używaj CLD przed operacjami stringowymi

    1
    
    cld          ; Zawsze przed LODSB, STOSB itd.
    
  4. Testuj skrajne przypadki

    • Pusta tablica
    • Jeden element
    • Maksymalne wartości

Częste błędy

  1. Off-by-one errors

    • Pętla wykonuje się o 1 za dużo/mało
    • Nieprawidłowa inicjalizacja licznika
  2. Błędna inkrementacja dla słów

    1
    2
    3
    4
    5
    
    ; ŹLE dla tablicy DW:
    inc si
    
    ; DOBRZE:
    add si, 2
    
  3. Niepoprawna inicjalizacja

    1
    2
    3
    4
    5
    
    ; ŹLE dla znajdowania max:
    mov al, 0
    
    ; DOBRZE:
    mov al, tablica[0]  ; Pierwszy element
    
  4. Zapomnienie o znaku liczb

    • JL vs JB (signed vs unsigned)
    • JG vs JA (signed vs unsigned)

Przydatne instrukcje do zadań

InstrukcjaOpisPrzykład
LOOP etykietaCX–, if CX≠0 jumpPętle liczące
INC reg/memZwiększ o 1inc si
DEC reg/memZmniejsz o 1dec cx
CMP op1, op2Porównaj (op1-op2)Ustawia flagi
JE/JZJump if Equal/ZeroPo CMP
JNE/JNZJump if Not EqualPo CMP
JL/JNGEJump if Less (signed)a < b
JG/JNLEJump if Greater (signed)a > b
JB/JNAEJump if Below (unsigned)a < b
JA/JNBEJump if Above (unsigned)a > b

6. Kryteria oceny

Każde zadanie oceniane jest według:

KryteriumPunkty
Program działa poprawnie40%
Znaleziono i opisano błąd30%
Wyjaśnienie architektury20%
Jakość kodu i dokumentacji10%

Uwaga: Nawet jeśli nie uda Ci się w pełni naprawić programu, możesz otrzymać punkty za:

  • Prawidłową identyfikację problemu
  • Merytoryczne wyjaśnienie
  • Próbę naprawy (nawet częściową)

Liczy się proces uczenia się i zrozumienie architektury!


7. Podsumowanie: Co powinniśmy wynieść z BLOKU 2

Po tym bloku powinieneś rozumieć:

  1. Jak procesor wykonuje pętle

    • Rola rejestru CX
    • Jak działa instrukcja LOOP
    • Alternatywy: CMP + warunkowe skoki
  2. Jak procesor adresuje tablice

    • Różnica między bajtami (DB) a słowami (DW)
    • Tryby adresowania: [SI], [BX+SI], [BX+5]
    • Dlaczego słowa wymagają inkrementacji o 2
  3. Specjalizowane instrukcje stringowe

    • LODSB/W, STOSB/W, MOVSB/W
    • Rola flag kierunku (CLD/STD)
    • Prefiksy REP, REPZ, REPNZ
  4. Mapowanie algorytmów C na asembler

    • Jak pętla for staje się LOOP
    • Jak indeksowanie tablicy arr[i] staje się [SI]
    • Różnice między wysokopoziomowymi a niskopoziomowymi konstrukcjami
  5. Debugowanie na poziomie architektury

    • Śledzenie rejestrów w czasie rzeczywistym
    • Obserwacja pamięci i stosu
    • Identyfikacja błędów związanych z architekturą (nie tylko logicznych)

To nie jest kurs programowania - to kurs zrozumienia jak komputer działa od środka!


Powodzenia!

Marcin Klimek
Architektura i Organizacja Systemów Komputerowych
WSB Nowy Sącz, 2025/2026