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
| |
Skrypt cl automatycznie wykonuje:
tasm program.asm- kompilacja do .OBJtlink program.obj- linkowanie do .EXE- Wyświetla ewentualne błędy
Debugowanie - TurboDebugger
| |
Podstawowe komendy:
F7- Step Into (krok do instrukcji)F8- Step Over (pomiń CALL)F9- Run to cursorCtrl+F2- Reset programuAlt+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
| |
Jak działa LOOP:
- Dekrementuje CX (CX = CX - 1)
- Sprawdza czy CX ≠ 0
- Jeśli tak - skacze do etykiety
- 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
| |
Porównanie:
| Metoda | Zalety | Wady |
|---|---|---|
| LOOP | Krótki kod, klasyczny | Tylko malejący licznik, ryzyko przy CX=0 |
| CMP+JMP | Dowolny kierunek, elastyczny | Dł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)
| |
ZAWSZE ustawiaj cld na początku operacji stringowych!
LODSB/LODSW - Load String
| |
STOSB/STOSW - Store String
| |
MOVSB/MOVSW - Move String
| |
CMPSB/CMPSW - Compare String
| |
SCASB/SCASW - Scan String
| |
Prefiksy powtarzania
| |
2.3. Adresowanie tablic
Podstawowe tryby
| |
Displacement (przesunięcie)
| |
Indeksowanie złożone
| |
Przesunięcie + indeks
| |
2.4. Różnica: bajt vs słowo w tablicach
KRYTYCZNE DO ZROZUMIENIA:
| |
Częsty błąd: Inkrementacja inc si dla tablicy słów - to da dostęp do środka słowa!
| |
3. Przykłady: C vs Asembler
Przykład 1: Pętla for - zliczanie
Kod C:
| |
Asembler x86-16 (TASM):
| |
Visual Studio (x64 - do demonstracji):
| |
Analiza różnic:
- DOS: Używamy
CXi instrukcjiLOOP- procesor to rozumie natywnie - x64: Kompilator używa jawnych porównań
CMPi skokówJL - DOS: 16-bit rejestry (AX, CX)
- x64: 32/64-bit rejestry (EAX, RAX)
Przykład 2: Tablica - suma elementów
Kod C:
| |
Asembler (TASM) - wersja 1 z LOOP:
| |
Asembler (TASM) - wersja 2 z LODSW:
| |
Analiza:
- Wersja 1: Jawne indeksowanie
tablica[si] - Wersja 2: Użycie instrukcji stringowej
LODSW- procesor automatycznie przesuwa SI - Oba podejścia są poprawne,
LODSWjest bardziej “idiomatic” dla x86
Przykład 3: String - długość łańcucha
Kod C:
| |
Asembler (TASM) - wersja 1 ręczna:
| |
Asembler (TASM) - wersja 2 z SCASB:
| |
Analiza:
- Wersja 1: Prosta, zrozumiała, jawna logika
- Wersja 2: Używa specjalizowanej instrukcji
SCASBz prefiksemREPNZREPNZ= 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:
| |
Co pokazać studentom w TD:
- View → Registers - obserwacja CX, SI, BL w czasie rzeczywistym
- View → Dump - zawartość pamięci, tablica w formacie heksadecymalnym
- View → Stack - jak wygląda stos programu
- F7 (Step) - wykonanie instrukcji krok po kroku
- Śledzenie jak:
CXmaleje z każdymLOOPSIrośnie co iteracjęBLakumuluje sumę- Flagi ZF, CF się zmieniają
4. Zadania do wykonania
Forma oddawania
Każde zadanie oddajemy jako:
- Kod źródłowy (.asm)
- Skompilowany program (.exe)
- 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:
| |
Twoje zadanie:
- Uruchom program i przetestuj
- Znajdź błąd (jest ich kilka - znajdź główny)
- Wyjaśnij:
- Co program robi źle?
- Jaki kod zwraca klawisz Enter w DOS? (sprawdź w skrypcie lub dokumentacji)
- Dlaczego licznik jest niepoprawny?
- 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:
| |
Twoje zadanie:
- Uruchom program i przetestuj z różnymi długościami tekstu
- Znajdź błąd związany z krótkim tekstem (np. 1 znak)
- Wyjaśnij:
- Co się dzieje gdy wpiszesz tylko 1 znak?
- Dlaczego program wyświetla nieprawidłowe znaki?
- Jaki jest stan SI po
dec sigdy SI=0?
- 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:
| |
Twoje zadanie:
- Uruchom program
- Program prawdopodobnie się zawiesi lub da błędny wynik - dlaczego?
- Znajdź błąd w pętli
- 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 sidla tablicydw? - Gdzie w pamięci SI będzie wskazywać po pierwszym
inc si?
- Jak duży jest element typu
- 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:
| |
Twoje zadanie:
- Uruchom program i sprawdź wynik
- Program nie znajduje prawidłowego maksimum - dlaczego?
- Znajdź błąd koncepcyjny
- 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?
- Wyjaśnij różnicę między instrukcjami:
JL(Jump if Less)JG(Jump if Greater)- Która jest poprawna dla naszego przypadku?
- 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:
| |
Twoje zadanie:
- Uruchom program i sprawdź wynik
- Program nie sortuje poprawnie - dlaczego?
- Znajdź błąd w warunku pętli wewnętrznej
- 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, cxjest poprawne? - Co powinno być prawidłowym warunkiem dla pętli wewnętrznej?
- Dodatkowe pytanie: Dlaczego używamy
jlezamiastjge? - 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
Zawsze inicjalizuj rejestry
1mov cx, 0 ; Jawna inicjalizacjaSprawdzaj rozmiary danych
1 2db = 1 bajt, inc si o 1 dw = 2 bajty, inc si o 2 (lub add si, 2)Używaj CLD przed operacjami stringowymi
1cld ; Zawsze przed LODSB, STOSB itd.Testuj skrajne przypadki
- Pusta tablica
- Jeden element
- Maksymalne wartości
Częste błędy
Off-by-one errors
- Pętla wykonuje się o 1 za dużo/mało
- Nieprawidłowa inicjalizacja licznika
Błędna inkrementacja dla słów
1 2 3 4 5; ŹLE dla tablicy DW: inc si ; DOBRZE: add si, 2Niepoprawna inicjalizacja
1 2 3 4 5; ŹLE dla znajdowania max: mov al, 0 ; DOBRZE: mov al, tablica[0] ; Pierwszy elementZapomnienie o znaku liczb
JLvsJB(signed vs unsigned)JGvsJA(signed vs unsigned)
Przydatne instrukcje do zadań
| Instrukcja | Opis | Przykład |
|---|---|---|
LOOP etykieta | CX–, if CX≠0 jump | Pętle liczące |
INC reg/mem | Zwiększ o 1 | inc si |
DEC reg/mem | Zmniejsz o 1 | dec cx |
CMP op1, op2 | Porównaj (op1-op2) | Ustawia flagi |
JE/JZ | Jump if Equal/Zero | Po CMP |
JNE/JNZ | Jump if Not Equal | Po CMP |
JL/JNGE | Jump if Less (signed) | a < b |
JG/JNLE | Jump if Greater (signed) | a > b |
JB/JNAE | Jump if Below (unsigned) | a < b |
JA/JNBE | Jump if Above (unsigned) | a > b |
6. Kryteria oceny
Każde zadanie oceniane jest według:
| Kryterium | Punkty |
|---|---|
| Program działa poprawnie | 40% |
| Znaleziono i opisano błąd | 30% |
| Wyjaśnienie architektury | 20% |
| Jakość kodu i dokumentacji | 10% |
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ć:
Jak procesor wykonuje pętle
- Rola rejestru CX
- Jak działa instrukcja LOOP
- Alternatywy: CMP + warunkowe skoki
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
Specjalizowane instrukcje stringowe
- LODSB/W, STOSB/W, MOVSB/W
- Rola flag kierunku (CLD/STD)
- Prefiksy REP, REPZ, REPNZ
Mapowanie algorytmów C na asembler
- Jak pętla
forstaje sięLOOP - Jak indeksowanie tablicy
arr[i]staje się[SI] - Różnice między wysokopoziomowymi a niskopoziomowymi konstrukcjami
- Jak pętla
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