C++ Tutorial

 Zeiger

Ein Zeiger ist ein Datum, welches auf eine Adresse im Speicher verweist. D.h., über einen Zeiger wird auf den Speicher zugegriffen.

Definition

Ein Zeiger wird wie folgt definiert:

DTYP *pName;

DTYP gibt an, wie die Daten zu interpretieren sind, deren Adresse im Zeiger pName abgelegt ist. Ist DTYP z.B. ein short, werden sizeof(short) Bytes (i.d.R. 2 Bytes) transferiert. Die eigentliche Definition als Zeiger erfolgt durch das kleine Sternchen vor dem Zeigernamen. Ohne das Sternchen wäre dies lediglich die Definition einer Variable.

Adressberechnung

Um die Adresse eines Datums zu erhalten, wird vor dem Namen des Datums der Adressoperator & angegeben. Hierbei muss DTYP des Zeigers gleichen dem Datentyp des Datums sein.

int main()
{
   // unsigned long Variable definieren/initialisieren
   unsigned long lvar = 0x11223344UL;
   // unsigned long Zeiger definieren und Adresse
   // der unsigned long Variable ablegen
   unsigned long *lPtr = &lvar;
}

Soll ein Zeiger auf eine fixe Adresse im Speicher verweisen, kann dem Zeiger eine Konstante zugewiesen werden. In diesem Fall muss eine entsprechende Typkonvertierung mittels reinterpret_cast<...> durchgeführt werden. reinterpret_cast<> wird auf der nächsten Seite noch näher erklärt.

unsigned char *ucptr = reinterpret_cast< unsigned char*>(0x1000);
constexpr auto MEMSTART = 0x1000UL;
const unsigned long *pMem = reinterpret_cast<const unsigned long*>(MEMSTART);

Zeigerzugriffe

Um auf den Inhalt der Speicherstelle zuzugreifen, deren Adresse im Zeiger abgelegt ist, ist vor dem Zeigernamen der Dereferenzierungsoperator * (wiederum das Sternchen) anzugeben. Die Anzahl der Bytes, die bei einem solchen Zugriff transferiert werden ist, wie erwähnt, vom Datentyp des Zeigers abhängig.

#include <iostream>
#include <format>

int main()
{
   // unsigned long Variable definieren/initialisieren
   unsigned long var = 0x11223344UL;
   // unsigned long Zeiger definieren und Adresse
   // der unsigned long Variable ablegen
   unsigned long *ptr = &var;
   // Daten ausgeben
   std::cout << std::format("var: {:#x}, *ptr: {:#x}\n", var,*ptr);
   // Speicher mit dem Wert 0x44556677 ueberschreiben, dessen
   // Adresse im Zeiger abgelegt ist
   *ptr = 0x44556677;
   // Daten erneut ausgeben
   std::cout << std::format("var: {:#x}, *ptr: {:#x}\n", var,*ptr);
}

String-Literale und Zeiger

Der Datentyp eines String-Literals ist standardgemäß const char[n] und damit kann ein String-Literal einem const char* zugewiesen werden (siehe auch Zuweisung von Zeichen- und String-Literalen).

Ausgabe von Zeigern

Soll mittels der Bibliotheksfunktion format() der Inhalt eines Zeigers, d.h. die in ihm abgelegte Adresse, ausgegeben werden, ist der Zeiger mittels reinterpret_cast in einen void-Zeiger zu konvertieren (siehe nachfolgendes Beispiel).

Eine Ausnahme davon bilden char-Zeiger. Bei char-Zeigern wird davon ausgegangen, dass der Zeiger auf einen String zeigt und somit der String auszugeben ist. Soll stattdessen der Zeigerinhalt ausgegeben werden, ist der Zeiger wieder in einen void-Zeiger zu konvertieren.

#include <iostream>
#include <format>

int main()
{
   // short Variable definieren/initialisieren
   short var = 0x1234;
   // short Zeiger definieren und ihm die Adresse
   // der short Variablen zuweisen
   short *pvar = &var;
   // Inhalt des Zeiger ausgeben
   // ACHTUNG! Der Zeiger muss mittels reinterpret_cast
   // fuer die Ausgabe in einen void-Zeiger konvertiert werden
   std::cout << std::format("Zeiger: {}\n",reinterpret_cast<void*>(pvar));
   // Datum ausgeben, Zeiger dereferenzieren
   std::cout << std::format("Datum: {:#}\n",*pvar);
   // const char Zeiger definieren/initialsieren
   const char *ptext = "Hallo!\n";
   // Inhalt des Zeigers ausgeben sowie den String, auf
   // den der Zeiger verweist
   // Hier ist keine Dereferenzierung notwendig!
   std::cout << std::format("Zeiger: {}, Text: {}\n", reinterpret_cast<const void*>(ptext), ptext);
}

nullptr

nullptr ist ein Literal für einen Zeiger, dessen Inhalt nicht definiert ist. Wird ein Zeiger definiert, dem bei seiner Definition keine Adresse zugewiesen werden kann, sollte der Zeiger mit einem nullptr initialisiert werden.

void Zeiger

Ein Zeiger vom Datentyp void* ist ein Zeiger, der an keinen Datentyp gebunden ist. Soll über einen solchen void-Zeiger auf Daten zugegriffen werden, muss dieser zuerst mittels reinterpret_cast in einen entsprechenden typisierten Zeiger (char*, short* usw.) konvertiert werden.

Operationen mit Zeigern

Ein Zeiger kann nur einem anderen Zeiger zugewiesen werden, wenn beide Zeiger den gleichen Datentyp besitzen. Soll eine Zuweisung mit einem anderen Datentyp erfolgen, ist dieser wieder mittels reinterpret_cast entsprechend zu konvertieren.

Für arithmetische Operationen mit Zeigervariablen gelten einige Besonderheiten:

  • Eine Addition eines Ganzzahlwertes X erhöht einen Zeiger vom Typ DTYP um sizeof(DTYP)*X. Für die Subtraktion gilt Entsprechendes.
  • Es können nur zwei Zeiger desselben Datentyps subtrahiert werden. Das Ergebnis ist vom Datentyp size_t.
  • Außer der erwähnten Addition und Subtraktion sind nur noch Vergleichsoperationen mit Zeigern erlaubt.
#include <iostream>
#include <format>

int main()
{
   // unsigned long Variabe definieren/initialisieren
   unsigned long var = 0xdeadbeafUL;
   // unsigned short Zeiger definieren und mit der Adresse
   // von var initialisieren
   unsigned short* ptr = reinterpret_cast<unsigned short*>(&var);
   // unsigned long Variable wortweise (16-Bit) ausgeben
   std::cout << std::format("{:x} als 16-Bit: {:x},", var, *ptr);
   // Zeiger auf naeachste Wort
   ptr++;
   // Naechstes Wort ausgeben
   std::cout << std::format("{:x}\n", *ptr);
}

const und Zeiger

Wie bereits weiter vorne weiter ausgeführt, werden für nicht veränderbare Werte Konstanten verwendet. Bei Zeiger sind dabei 3 Fälle zu unterscheiden:

  • Der Zeiger ist konstant.
  • Das, worauf der Zeiger zeigt, ist konstant.
  • Sowohl der Zeiger als auch das, worauf er zeigt, ist konstant.

Die entsprechenden Zeiger-Definitionen dazu sehen wie folgt aus:

Zeigerdefinition
Bedeutung
const DTYP *ptr;
Zeiger ptr zeigt auf eine Konstante vom Typ DTYP; der Zeiger kann verändert werden.
DTYP *const ptr;
Zeiger ptr zeigt auf Variable vom Typ DTYP; der Zeiger selbst ist konstant.
const DTYP *const ptr;
Sowohl der Zeiger ptr wie auch das, worauf er zeigt, ist konstant.