Sesja 9 — Rejestry: więcej niż jeden akumulator
✅ Sprawdź się z sesji 8
Cel
Zastąpić pojedynczy akumulator z poprzednich sesji tablicą kilku “rejestrów” — dokładnie tak, jak robi to prawdziwy procesor (i CHIP-8, z jego 16 rejestrami V).
Teoria w pigułce
Ograniczenie jednego akumulatora
Maszyna z sesji 5-8 ma tylko jedno miejsce na wynik — akumulator. Nie da się nią policzyć np. (a + b) * (c - d) bez zgubienia jednej z wartości pośrednich. Prawdziwe procesory mają wiele rejestrów właśnie po to, żeby trzymać kilka wartości naraz.
Tablica jako zestaw rejestrów
int rejestry[4] = {0, 0, 0, 0}; // R0, R1, R2, R3
rejestry[0] = 10;
rejestry[1] = 5;
rejestry[2] = rejestry[0] + rejestry[1]; // R2 = R0 + R1Zauważ: to nie jest nowa koncepcja C — to ta sama tablica, którą znacie od sesji 5, tylko interpretowana inaczej: każdy indeks to teraz osobne miejsce na wynik, a nie kolejna instrukcja programu.
Instrukcja z dwoma operandami: numer rejestru
Żeby instrukcja mogła powiedzieć “dodaj do rejestru R2”, sama instrukcja musi przechowywać numer rejestru docelowego, nie tylko wartość:
struct Instrukcja {
enum OpCode kod;
int rejestr_docelowy; // ktory rejestr modyfikujemy
int wartosc; // literal, albo numer innego rejestru -- zalezy od operacji
};V[x] z CHIP-8
6XKK (LD Vx, byte) w CHIP-8 to dokładnie ten wzorzec: “X” mówi, KTÓRY z 16 rejestrów V ma dostać wartość “KK”. Struktura, którą dziś zbudujecie, ma trzy pola z dokładnie tego samego powodu, dla którego opcode CHIP-8 koduje w sobie zarówno typ operacji, jak i numer rejestru.
Zadania
Zadanie 1 — rozbuduj maszynę o rejestry
Zmień struct Instrukcja, dodając pole int rejestr_docelowy. Zmień execute_instruction, żeby przyjmowała int rejestry[] (całą tablicę) zamiast pojedynczego int akumulator, i modyfikowała rejestry[instr.rejestr_docelowy] zamiast zwracać nową wartość akumulatora:
void execute_instruction(int rejestry[], struct Instrukcja instr) {
switch (instr.kod) {
case OP_LOAD:
rejestry[instr.rejestr_docelowy] = instr.wartosc;
break;
case OP_ADD:
rejestry[instr.rejestr_docelowy] += instr.wartosc;
break;
// ...
}
}Zwróć uwagę: funkcja jest teraz void, nie int — nie zwraca wartości, tylko modyfikuje tablicę, która została jej przekazana. To działa (w odróżnieniu od przykładu z zwieksz(int x) z sesji 6!), bo tablice w C zachowują się inaczej niż pojedyncze zmienne przy przekazywaniu do funkcji — do tego wrócimy dokładnie w sesji 11.
Zadanie 2 — operacja między dwoma rejestrami
Dodaj OP_ADD_REG — dodaje wartość jednego rejestru do drugiego (nie literał). Dla tej operacji pole wartosc przechowuje indeks rejestru źródłowego. Nie dodajemy czwartego pola: znaczenie argumentu zależy od kodu operacji. Przed dostępem sprawdź zakres obu indeksów.
case OP_ADD_REG:
rejestry[instr.rejestr_docelowy] += rejestry[instr.wartosc];
break;Zadanie 3 — policz coś, co NAPRAWDĘ wymaga kilku rejestrów
Uwaga: nie każde wyrażenie z nawiasami tego wymaga — (3 + 4) * 2 policzycie jednym akumulatorem bez problemu (LOAD 3, ADD 4, MUL 2 — żadna wartość pośrednia nie ginie, bo liczymy liniowo, krok po kroku). Rejestrów potrzeba dopiero, gdy musicie jednocześnie pamiętać dwie niezależnie policzone wartości, zanim je złączycie — np. (3 + 4) + (10 + 20): żeby dodać do siebie wynik 3+4 i wynik 10+20, musicie mieć oba w pamięci naraz (jeden akumulator nadpisałby pierwszy wynik, licząc drugi).
Napisz “program” (tablicę struct Instrukcja) liczący (3 + 4) + (10 + 20): policz pierwszą sumę w R0, drugą w R1, potem OP_ADD_REG złącz je w jednym z rejestrów. Wypisz zawartość rejestrów na końcu (przez OP_PRINT albo wypisz_rejestry).
Kryterium sukcesu
- Maszyna operuje na tablicy kilku rejestrów zamiast jednego akumulatora.
OP_ADD_REGpoprawnie dodaje wartość jednego rejestru do drugiego.- Program testowy z zadania 3 daje poprawny wynik (
37), korzystając z więcej niż jednego rejestru po drodze.