Was ist Zeiger (Pointer)?

Variable, deren Wert die Speicheradresse einer Variablen, eines Array-Elements, einer Funktion o. Ä. ist. In C wird ein Zeiger etwa als int *p deklariert; &x liefert die Adresse von x, und *p liest oder schreibt den Wert, auf den p zeigt. Addiert man eine Ganzzahl zu einem Zeiger, bewegt er sich in Einheiten der Größe des Typs, auf den er zeigt. Der Name eines Arrays wird in den meisten Ausdrücken in einen Zeiger auf das erste Element umgewandelt, und a[i] bedeutet dasselbe wie *(a + i). Der Wert, der für „zeigt auf nichts“ steht, heißt NULL-Zeiger.

Japanische Bezeichnung: ポインタ

„Zeiger (Pointer)“ kommt in 23 Übungsfragen aus 7 Themen vor, z. B. Grundlagen der Programmiersprache C, Datenstrukturen und Algorithmen, Befehlssatz und Assembler.

Fachgebiet: Grundlagen der Programmiersprache C

Deklaration und Grundoperationen

int *p; deklariert p als „Zeiger auf int“. Speichert man mit p = &x; die Adresse der Variablen x, schreibt *p = 5; den Wert 5 in x. Ein Argument wird an eine Funktion als Wert übergeben; soll eine Funktion eine Variable des Aufrufers ändern, übergibt man deren Adresse als Zeiger. Um über einen Zeiger auf eine Struktur (struct) auf einen Member zuzugreifen, schreibt man statt (*p).m kurz p->m.

Zeigerarithmetik und Arrays

Addiert man 1 zu einem Zeiger p auf int, rückt die Adresse nicht um 1 Byte, sondern um sizeof(int) Byte vor (in den meisten Umgebungen 4 Byte). Der Name eines Arrays wird, außer etwa als Operand von sizeof oder &, in einen Zeiger auf das erste Element umgewandelt. Übergibt man ein Array als Argument an eine Funktion, wird daher in Wirklichkeit nur die Adresse des ersten Elements übergeben, und sizeof liefert in der Funktion nicht die Größe des gesamten Arrays.

Verwendung in Embedded-Systemen und Vorsicht

Auf einem Mikrocontroller wandelt man die Adresse eines Peripherieregisters aus einer Ganzzahl in einen Zeiger um, etwa (volatile uint32_t *)0x40020014, und liest oder schreibt darüber. Lesen oder Schreiben über einen nicht initialisierten Zeiger, einen NULL-Zeiger oder einen Dangling Pointer, der auf einen freigegebenen Bereich zeigt, ist undefiniertes Verhalten; in Umgebungen ohne Betriebssystem kann es unbemerkt andere Daten zerstören.

Jetzt üben: Fragen zu Zeiger (Pointer)

Welche Aussage beschreibt eine verkettete Liste (eine Liste, in der jedes Element einen Zeiger auf das nächste Element enthält) im Vergleich zu einem Array richtig?

  1. AIst das Element direkt vor der Einfügestelle bekannt, lässt sich ein neues Element allein durch Umsetzen von Zeigern einfügen, ohne andere Elemente zu verschieben; um das i-te Element zu lesen, muss die Liste aber von vorn durchlaufen werden
  2. BDie Position des i-ten Elements lässt sich berechnen, daher kann jedes Element in konstanter Zeit O(1) gelesen werden
  3. CDie Elemente liegen lückenlos hintereinander im Speicher, daher wirken Cache und Prefetching beim sequenziellen Lesen gut
Antwort und Erläuterung anzeigen

Antwort: A. Ist das Element direkt vor der Einfügestelle bekannt, lässt sich ein neues Element allein durch Umsetzen von Zeigern einfügen, ohne andere Elemente zu verschieben; um das i-te Element zu lesen, muss die Liste aber von vorn durchlaufen werden

In einer verketteten Liste genügt es zum Einfügen und Löschen an einer bekannten Stelle, die benachbarten Zeiger umzuschreiben; das kostet O(1). Um das i-te Element zu lesen, muss man dagegen von vorn i Zeigern folgen, was O(n) dauert. Ein Array berechnet die Adresse des i-ten Elements aus der Startadresse und der Elementgröße und liest jedes Element in O(1); das Einfügen in der Mitte kostet aber O(n), weil die nachfolgenden Elemente verschoben werden müssen. Da die Elemente eines Arrays zusammenhängend liegen, wirken außerdem Cache und Prefetching gut, während die Knoten einer verketteten Liste oft im Speicher verstreut sind; selbst ein Durchlauf mit gleichem O(n) kann dann deutlich langsamer sein.

int habe 4 Byte und ein Zeiger 8 Byte. Ein Zeiger p auf int hat den Wert (die Adresse) 0x1000. Welchen Wert hat p + 3?

  1. A0x100C
  2. B0x1012
  3. C0x1018
Antwort und Erläuterung anzeigen

Antwort: A. 0x100C

Addiert man eine Ganzzahl zu einem Zeiger, bewegt er sich in Einheiten der Größe des Typs, auf den er zeigt. int hat 4 Byte, daher zeigt p + 3 auf die Stelle 3 × 4 = 12 Byte weiter, also 0x1000 + 0xC = 0x100C. p + 3 ist die Adresse des Array-Elements p[3], und *(p + 3) bedeutet dasselbe wie p[3]. Ein Zeiger auf char bewegt sich in Schritten von 1 Byte, ein Zeiger auf double (8 Byte) in Schritten von 8 Byte.

Häufige Fragen

Wie groß ist ein Zeiger?

In den meisten 64-Bit-Umgebungen 8 Byte, auf 32-Bit-Mikrocontrollern 4 Byte. Ob er auf char oder double zeigt, ein Zeiger auf Daten ist im Allgemeinen gleich groß. Da ein Zeiger nicht unbedingt so groß wie int ist, verwendet man uintptr_t aus stdint.h, wenn eine Adresse in einer Ganzzahl gespeichert werden soll.

Was ist der Unterschied zwischen const char *p und char *const p?

const char *p ist ein „Zeiger auf const char“: Über p können keine Zeichen geändert werden, p selbst kann aber auf eine andere Adresse gesetzt werden. char *const p ist ein „konstanter Zeiger“: p kann nicht geändert werden, das Ziel aber schon. Merkregel: Steht const links vom *, ist das Ziel festgelegt, steht es rechts davon, der Zeiger selbst.

„Zeiger (Pointer)“ in den Erläuterungen

Fragen mit „Zeiger (Pointer)“ (23)

  1. Welche Aussage beschreibt eine verkettete Liste (eine Liste, in der jedes Element einen Zeiger auf das nächste Element enthält) im Vergleich zu einem Array richtig?
  2. int habe 4 Byte und ein Zeiger 8 Byte. Ein Zeiger p auf int hat den Wert (die Adresse) 0x1000. Welchen Wert hat p + 3?
  3. int habe 4 Byte und ein Zeiger 8 Byte. Welche Ausgabe erzeugt das obige Programm bei der Ausführung?
  4. Was trifft auf den Zeiger zu, den die obige Funktion make zurückgibt?
  5. Was trifft in C auf die Dereferenzierung eines NULL-Zeigers zu (wenn *p gelesen wird, während p NULL ist)?
  6. Wo werden das 9. und 10. Argument übergeben, wenn in der Standard-Aufrufkonvention von RISC-V eine Funktion mit 10 Ganzzahlargumenten aufgerufen wird?
  7. Welche Beschreibung des ABI (Application Binary Interface) ist richtig?
  8. Warum kann der Compiler in der obigen Funktion die Optimierung, das Lesen von *b aus der Schleife herauszuziehen, nicht ohne Weiteres durchführen?
  9. Welche Befehlsart wird bei der automatischen Vektorisierung des Compilers verwendet?
  10. Was geschieht im Allgemeinen, wenn ein Programm auf eine vom Betriebssystem nicht zugewiesene Adresse zugreift?
  11. Welche Aussage zum ABA-Problem, das bei sperrfreien Datenstrukturen mit CAS auftritt, ist richtig?
  12. Welche Aussage zu Unified Memory von CUDA ist richtig?
  13. Ein binärer Heap wird mit der Wurzel in a[0] in einem Array a[0], a[1], a[2], … gespeichert, wobei jede Ebene von oben beginnend von links nach rechts aufgefüllt wird. Welches sind die Kinder und der Elternknoten von a[4]?
  14. Warum ist bei einer Hashtabelle mit vielen Elementen und einem Belegungsfaktor von etwa 0,5 die offene Adressierung mit linearem Sondieren am ehesten schneller als die Verkettung (Verbinden der Elemente durch verkettete Listen)?
  15. Was legt der C-Standard über die Größe des Typs int fest?

Verwandte Begriffe

Array Undefiniertes Verhalten (Undefined Behavior) Variable Argument Stack (Stapelspeicher) Mikrocontroller Prefetching Dynamische Speicherreservierung Seitenfehler (Page Fault) Aufrufkonvention

Zur Glossarübersicht