Fr. Apr 19th, 2024

In der Sprache C existiert ein Konstrukt das als Zeiger oder Pointer bezeichnet wird. Zeiger repräsentieren eine besondere Art von Variablen. Ihr Inhalt ist eigentlich nebensächlich. Viel interessanter ist, dass sie auf andere Variablen zeigen können. Man betrachtet also nicht die Zeigervariable selbst, sondern über die Zeigervariable auf den Inhalt einer anderen Variablen.

Diese Indirektion ist nicht ganz leicht zu verstehen und bietet auch bei erfahrenen Programmieren immer wieder Anlass zur Verwirrung. Aus diesem Grund soll im folgenden Beitrag die Funktionsweise eines Zeigers erklärt werden.

Variablen

Eine Variable wird zum speichern von Informationen verwendet. Sie muss üblicherweise deklariert werden. D.h. man ordnet ihr einen eindeutigen Namen und einen Datentyp zu. Diese Informationen werden vom Compiler dann in einer sogenannten Symboltabelle abgelegt und verwaltet.

Nachdem man beispielsweise folgende Deklaration gemacht hat:

int myVar;

Wird in der Symboltabelle folgender Eintrag vorgenommen:

Bei diesem Vorgang fordert der Compiler vom Speichermanagement des Systems eine Adresse an, wo die damit zusammenhängenden Informationen dann gespeichert werden. Diese Speicheradresse wird als I-Wert bezeichnet, und gibt an, wo sich die Variable im Speicher befindet also (I-Wert = Standort).

Wenn wir einer Variablen nun einen konkreten Wert zuweisen, navigieren wir direkt zum Speicherort der Variablen im Speicher (dem I-Wert) und aktualisieren den Speicher an dieser Adresse mit dem neuen Wert. Die Daten, die tatsächlich im Speicher gespeichert sind, werden als R-Wert (R-Wert = Registerwert ) bezeichnet. Die Anzahl der dazu benötigten Speicherplätze hängt massgeblich vom Typ der Variablen ab.

Zeiger

Nachdem wir uns nun mit den Variablen und ihrer Funktionsweise vertraut sind, können wir uns dem Zeiger-Konstrukt zu wenden. Einfach ausgedrückt ist ein Zeiger nichts anderes als eine Variable, die auf die Speicheradresse einer anderen Variablen verweist. 

Unter Verwendung der soeben erlernten Terminologie ist ein Zeiger somit eine Variable, deren R-Wert der I-Wert einer anderen Variablen ist.

Die Deklaration einer Zeiger-Variable ist recht einfach. Um eine Zeigervariable zu definieren, wird zunächst der Typ angegeben, auf den der Zeiger zugreifen kann. Es folgt ein Stern und dann der Name der Variablen:

int *myPointer;

Der Typbezeichner (in diesem Fall int) muss mit dem Datentyp der Variablen übereinstimmen, mit der der Zeiger verwendet werden soll. Das Sternchen zeigt dem Compiler an, dass myPointer ein Zeiger ist. Da Leerzeichen in C keine Rolle spielen, kann das Sternchen an einer beliebigen Stelle zwischen dem Typbezeichner und dem Namen der Zeigervariablen platziert werden, so dass manchmal auch Folgendes angezeigt wird: int * myPointer, int * myPointer usw.

Ein Zeiger, der definiert ist, aber nicht auf irgendetwas zeigt, ist für sich genommen ein ziemlich sinnloser Zeiger. Um auf die Speicheradresse einer anderen Variablen zu verweisen, müssen wir dem Zeiger natürlich die Speicheradresse dieser Variablen zuweisen.

Dazu wird der sogenannte „Adress-of-Operator &“ verwendet. Um unseren neuen Zeiger auf den Speicherort unserer Werttypvariablen myVar zu richten, rufen wir einfach die folgende Anweisung auf:

myPointer = &myVar;

Dies vervollständigt den im vorherigen Diagramm gezeigten Link und wird als Referenzierung bezeichnet . Aus dem gleichen Grund wird der Adressoperator (&) auch als „Referenzierungsoperator“ bezeichnet.

Beispiel mit der Arduino IDE

Machen wir ein Beispiel für das, was wir bisher besprochen haben in der Arduino IDE :

void setup() {
  Serial.begin(9600);
  
  int myVar = 10;  // Initialize a variable.
  
  Serial.print("myVar's lvalue: ");
  Serial.println((long) &myVar, DEC);  // Grab myVar's lvalue
  Serial.print("myVar's rvalue: ");
  Serial.println(myVar, DEC);
  Serial.println();
  
  int *myPointer;   // Declare your pointer.
  myPointer = &myVar; //Assign myVar's memory address to pointer.
  
  Serial.print("myPointer's lvalue: ");
  Serial.println((long) &myPointer, DEC);  //myPointer's lvalue
  Serial.print("myPointer's rvalue: ");
  Serial.println((long) myPointer, DEC);  //myPointer's rvalue
}

void loop() {
}

Als Ergebnis erhalten wir auf dem Monitor folgende Ausgabe:

Monitor Ausgabe des Zeiger-Beispiel Programms

De-Referenzierung

Wir haben gerade gesehen, dass ein Zeiger auf eine Stelle im Speicher verweisen kann, indem er diesem Zeiger die Speicheradresse einer Variablen mit dem Referenzoperator (&) zuweist.

Wir können noch einen Schritt weiter gehen und den an dieser Speicheradresse gespeicherten tatsächlichen Wert erhalten, indem wir den Zeiger dereferenzieren. Dies wird auch als Indirektion bezeichnet und erfolgt über den Indirektionsoperator (*) mit Ihrem Zeiger.

*myPointer = 5; // Go to memory addressed stored in myPointer's rvalue (myVar's lvalue)
                //  and place the value 5 in that memory address.
De-Referenzierung am Beispiel

Wir erweitern das obige Programm und erhalten folgendes Ergebnis:

void setup() {
  Serial.begin(9600);
  int myVar = 10;
  
  Serial.print("myVar's lvalue: ");
  Serial.println((long) &myVar, DEC);
  Serial.print("myVar's rvalue: ");
  Serial.println(myVar, DEC);
  Serial.println();
  
  int *myPointer;
  myPointer = &myVar;
  
  Serial.print("myPointer's lvalue: ");
  Serial.println((long) &myPointer, DEC);
  Serial.print("myPointer's rvalue: ");
  Serial.println((long) myPointer, DEC);
  Serial.println();

  *myPointer = 5;  //THIS IS OUR DEREFRENCING ADDITION.
  Serial.println("-----------------------");
  Serial.println("Updating *myPointer = 5");
  Serial.println();

  Serial.print("myPointer's lvalue: ");
  Serial.println((long) &myPointer, DEC);
  Serial.print("myPointer's rvalue: ");
  Serial.println((long) myPointer, DEC);
  Serial.println();

  Serial.print("myVar's lvalue: ");
  Serial.println((long) &myVar, DEC);
  Serial.print("myVar's rvalue: ");
  Serial.println(myVar, DEC);
  Serial.println();
}
void loop() {
}