Entscheidungen & Schleifen

Entscheidungen & Schleifen

Standardmäßig führt die CPU deine Anweisungen in der Reihenfolge aus, in der sie erscheinen, von oben nach unten. Für sich genommen kann diese Reihenfolge niemals eine Wahl treffen oder Arbeit wiederholen. Ein Branch ist die Art und Weise, wie ein Programm diesen geraden Pfad verlässt: Er prüft eine Bedingung und, wenn die Bedingung zutrifft, weist die CPU an, an einer anderen Stelle fortzufahren statt in der nächsten Zeile. Jede Entscheidung und jede Schleife entsteht aus diesem einen Mechanismus.

Es gibt kein if und kein for. Du konstruierst sie aus Labels (benannten Positionen) und Branches (Sprung zu einer Position, wenn eine Bedingung zutrifft). Ein Label ist ein Name, gefolgt von :.

Um zu verstehen, wie ein Branch ausgeführt wird, musst du den Program Counter (PC) kennen. Der PC ist ein spezielles Register, das vor deinem Code verborgen ist und die Speicheradresse der gerade ausgeführten Anweisung enthält. Eine Branch-Anweisung schreibt einen neuen Wert in den PC, sodass die CPU ihre nächste Anweisung vom Ziel-Label statt aus der folgenden Codezeile holt.

Die Verzweigungsfamilie

Jeder Branch vergleicht zwei Register und springt nur dann zum Label, wenn der Test zutrifft. Wenn der Test fehlschlägt, wird die Ausführung in der nächsten Zeile fortgesetzt. Sechs Tests bilden den Kern:

Die sechs echten Branches
beq a, b, label # verzweige, wenn a == b
bne a, b, label # verzweige, wenn a != b
blt a, b, label # verzweige, wenn a < b (vorzeichenbehaftet)
bge a, b, label # verzweige, wenn a >= b (vorzeichenbehaftet)
bltu a, b, label # verzweige, wenn a < b (vorzeichenlos)
bgeu a, b, label # verzweige, wenn a >= b (vorzeichenlos)

und prüfen Gleichheit und ignorieren das Vorzeichen. und bilden das Ordnungspaar, kleiner als und größer oder gleich, und vergleichen die Register als vorzeichenbehaftete Zahlen. und führen dieselben Vergleiche durch, behandeln die Werte aber als vorzeichenlos, was für Adressen oder Zähler passt, die nie negativ sind.

Was bedeuten vorzeichenbehaftet und vorzeichenlos? Ein Register enthält 32 Bits, und dieselben Bits können auf zwei Arten gelesen werden. Als vorzeichenlos gelesen bilden sie eine einfache ganze Zahl von 0 bis etwa 4 Milliarden. Als vorzeichenbehaftet gelesen zählt das oberste Bit als Minuszeichen, sodass die Hälfte der Bitmuster negative Zahlen darstellt. Die Bits selbst ändern sich nicht. Nur die Art, wie du sie liest, ändert sich. Deshalb gibt es zwei Vergleichssätze: Verwende die vorzeichenbehafteten (, ) für gewöhnliche Zahlen, die negativ sein können, und die vorzeichenlosen (, ) für Werte, die nie negativ sind, wie Speicheradressen.

Es gibt kein direktes "Branch wenn größer" oder "Branch wenn kleiner oder gleich". Du erhältst sie, indem du die beiden Register vertauschst, und der Assembler stellt und als Pseudo-Branches bereit, die die Vertauschung für dich durchführen, sodass zu wird. Zwei weitere Abkürzungen: und testen ein Register gegen null, und springt ganz ohne Bedingung.

Summiere 1..5 mit einer Schleife
li t0, 0 # Summe = 0
li t1, 1 # i = 1
li t2, 6 # Grenze = 6 (Schleife solange i < 6)
loop:
bge t1, t2, done # verlässt die Schleife, wenn i >= 6
add t0, t0, t1 # Summe += i
addi t1, t1, 1 # i++
j loop # erneut prüfen
done:
mv a0, t0 # gibt die Summe aus (sollte 15 sein)
li a7, 1
ecall
li a7, 10
ecall

Verfolge es. t0 enthält die laufende Summe (beginnt bei 0), t1 enthält den Zähler i (beginnt bei 1), und t2 enthält das Limit 6. Bei jedem Durchlauf von loop: verlässt die Schleife, sobald i 6 erreicht. Andernfalls addiert i zur Summe, erhöht i um eins, und springt zurück, um erneut zu testen. Die Schleife addiert 1, 2, 3, 4 und 5, und done gibt die Summe 15 aus.

Schleife mit einer Verzweigung
loop:
bge t1, t2, done
add t0, t0, t1
addi t1, t1, 1
j loop
i (t1)1
ich (t0)0
janot1 < 6?sum += t1t1 += 1fertig

bge vergleicht t1 mit t2, um zu entscheiden, ob die Schleife fortgesetzt wird.

Die folgende Abbildung markiert die Anweisung, die den Zähler aktualisiert, und diejenige, die die laufende Summe aktualisiert, und hebt sie vom Rest der Schleife ab. erhöht den Zähler, und das darauffolgende addiert den neuen Zählerwert zur Summe. Das letzte Bild zeigt den Test, der die Schleife beendet. Wenn t1 zu 6 wird, überträgt die Kontrolle an done, statt den Rumpf erneut auszuführen. Dieser Test ist es, der einen Durchlauf, der sich wiederholt, von dem Durchlauf trennt, der stoppt.
💡
Gehe mit Step statt Run durch das Programm. Während du voranschreitest, erhöht sich t1 und der Program Counter (pc) springt zurück zu loop. Jedes Mal, wenn pc zu loop zurückkehrt, war die Branch-Bedingung falsch, also läuft die Schleife erneut.
⚠
Wenn die Abbruchbedingung falsch ist, endet die Schleife nie. Der Simulator stoppt eine durchgehende Schleife, aber die Branch-Logik liegt bei dir, sie richtig hinzubekommen.