Der lise Blog:
Einsichten, Ansichten, Aussichten

C++: Mit der Metaplanwand Zeiger erklären

Meine Kollegen Ronny Schwierzinski und Kevin Gath haben zusammen mit mir eine kleine Schulung zum Thema „C++ in a Nutshell“ gehalten. Mit dieser Mini-Schulung wollten wir unsere Kollegen für eine Sprache begeistern, die aufgrund ihrer manuellen Speicherverwaltung einen schlechteren Ruf hat, als sie verdient.

Jeder Kurs, jedes Buch und jeder Vortrag über C/C++ kommt um ein Thema nicht vorbei: Zeiger. Zeiger sind in der Softwareentwicklung unverzichtbar. In moderneren Sprachen ist ihr Einsatz nur noch im Hintergrund zu erahnen – ihre Mechanik ist für viele Menschen schwer zu erfassen. Eine wiederkehrende Aufgabe ist es daher, Entwicklern die Natur und das Verhalten von Zeigern nachvollziehbar zu erklären.

 

Das RAM-Board

In unserer Schulung war es meine Aufgabe, dieses unbeliebte Thema näher zu bringen. Hier wählte ich eine Kombination aus Code-Schnipseln, „Pseudo-Assembler“ und einer Metaplanwand, welche ich in ein „RAM-Board“ umfunktionierte.

Das RAM-Board sollte den Inhalt eines Arbeitsspeichers visualisieren: Ein großer Speicherbereich mit vielen 4-Byte-Blöcken (siehe Bild). Die Speicheradressen werden mit hexadezimalen Adressen gekennzeichnet (im Bild: rot und an der linken Seite). An jeder Speicheradresse kann man Daten hinterlegen.

In der Realität zeigen nicht alle Speicheradressen in den Arbeitsspeicher. So reserviert sich das Betriebssystem Adressen, um Register oder externe Hardware zu adressieren. Im RAM-Board wird dies dargestellt mit einem Speicherbereich, der durch Totenköpfe gekennzeichnet ist. Ein Zugriff darauf ist tabu (Die beliebten Speicherzugriffsfehler resultieren daraus und sind artverwandt mit den NullPointerExceptions).

 

Daten auf dem RAM-Board

Die Daten werden durch Zettel dargestellt, die auf der Vorderseite in menschenlesbarer Form und auf der Rückseite als hexadezimale „Roh“-Daten dargestellt sind (im Bild sehen wir die ASCII-Werte).

Auf den Zetteln finden sich auch Zeiger wieder (siehe Bild): Sie stellen RAM-Adressen dar, weswegen ich die Rückseite auch hexadezimal darstelle. (Idealerweise wäre die Adresse in Analogie zu den Adressen im RAM-Board auch in rot – aber das hatte ich übersehen!)

Code-Schnipsel und Pseudo-Assembler

Das Übertragen von kleinen Code-Schnipseln in „Pseudo-Assembler“ soll beim Verständnis helfen. Dabei werden lediglich die Variablen-Namen und Werte so ausgetauscht, wie sie im Speicher hinterlegt werden würden. Der Anspruch auf Vollständigkeit ist definitiv nicht vorhanden. Viel wichtiger ist die Veranschaulichung, dass Variablen auf Adressen gemappt werden.

 

Ein Wert im Arbeitsspeicher

Eine Variablendeklaration und die Zuweisung eines Wertes lässt sich so auf dem RAM-Board gut darstellen. Wichtig ist immer das Zusammenspiel von Pseudo-Assembler und dem RAM-Board.

Nehmen wir als Beispiel die obige Zuweisung:

Im Pseudo-Assembler sehen wir die Reservierung für Rechtecke an den Adressen 0x18 und 0x30. Zunächst (Schritt [1]) werden die Werte 3 und 5 dem ersten Rechteck zugewiesen. Auf dem RAM-Board hängen wir dafür die entsprechenden Werte an die Adressen 0x18 und 0x1C:

Anschließend (Schritt [2]) werden durch die Zuweisung die Werte vom ersten Rechteck an die Adresse des zweiten Rechtecks (0x30) kopiert:

Anhand dieses sehr einfachen Beispiels lässt sich dank des RAM-Boards eine Mechanik erkennen, die C/C++ von Sprachen mit automatischer Speicherverwaltung unterscheidet: Zuweisungen sind immer Kopiervorgänge. Bei C# oder Java werden hingegen nur Referenzen kopiert.

 

Ein Zeiger im Arbeitsspeicher

Mit dem RAM-Board lässt sich sehr einfach die Mechanik von Zeigern visualisieren. Nehmen wir folgendes Beispiel:

Zunächst werden den Variablen a und b (Schritte [1] und [2]) die beiden Werte 3 und 30 zugewiesen:

 

Anschließend ([3]) wird der Zeiger mit der Adresse von Variable a (0x30) deklariert und initialisiert:

 

In Schritt 4 ([4]) wird der Zeiger dereferenziert und die dahinterstehende Variable (a) beschrieben:

 

Der Zeiger zeigt dann auf ein neues Ziel (Schritt [5]) …

 

… um die dahinter referenzierte Variable (b) über den Zeiger neu zuweisen zu können (Schritt [6]):

 

Durch einen Blick auf das RAM-Board werden Zeiger entzaubert und deren Funktionsweise anhand von greifbaren Werten visualisiert. Die kompliziertere Handhabung von Zeigern kann so visualisiert und besser analysiert werden. So konnten wir dank des RAM-Boards eng verknüpfte Legacy-Code-Stellen verstehen, welche Zeiger auf Zeiger umfangreich verwendeten.

 

Viele weitere Verwendungsmöglichkeiten des RAM-Boards

Das RAM-Board bietet noch mehr Möglichkeiten um die teils stark abstrahierte Technik verständlich und greifbar zu machen. Ein Auszug:

  • Byte-Reihenfolge/Endianess
  • unitialisierter Speicher
  • reinterpret_cast
  • Speicher-Bereiche (Heap/Stack/System-reserviert)
  • Smart-Pointer

 

Das RAM-Board zum Visualisieren von hardwarenahen Vorgängen

Das RAM-Board ermöglicht uns nicht nur die Schulung von anderen Kollegen in hardwarenaher Programmierung, sondern erleichtert auch die Analyse und Visualisierung von komplizierten Legacy­systemen. Es gehört nicht zu unseren Tools des täglichen Arbeitens, aber erweist sich immer wieder als nützlich.

 

Diesen Artikel weiterempfehlen

 Teilen  Teilen  Teilen  Teilen