Zeiger (C)
Der Artikel Zeiger in C beschreibt die Verwendung von Zeigern in der Programmiersprache C. Zeiger sind Variablen, in denen man Speicheradressen speichert. Sie werden in C häufig eingesetzt und sind für manche Programmierkonzepte die einzige Möglichkeit der Realisierung.
Definition
Die Definition eines Zeigers besteht aus dem Datentyp des Zeigers und dem gewünschten Zeigernamen. Der Datentyp eines Zeigers besteht wiederum aus dem Datentyp des Werts auf den gezeigt wird sowie aus einem Asterisk. Ein Datentyp eines Zeigers wäre also z. B. double*
.
int* zeiger1; /* kann eine Adresse aufnehmen, die auf einen Wert vom Typ Integer zeigt */
int *zeiger2; /* das Leerzeichen kann sich vor oder nach dem Stern befinden */
int * zeiger3; /* ebenfalls möglich */
int *zeiger4, *zeiger5; /* Definition von zwei Zeigern */
int *zeiger6, ganzzahl; /* Definition eines Zeigers und einer Variablen vom Typ Integer */
Ein definierter Zeiger, der noch nicht initialisiert wurde, zeigt auf eine zufällige Adresse. Bei einem Zugriff auf diese Adresse kann es zu einem Programmabsturz oder zum Überschreiben des an dieser Adresse gespeicherten Wertes kommen. Zeiger sollten also immer initialisiert werden.
Zuweisungen
Die Zuweisung einer Adresse an einen Zeiger erfolgt mithilfe des Adressoperators, eines Feldes, eines weiteren Zeigers oder des Wertes von NULL
.
int variable = 0;
int feld[10];
int *zeiger;
int *zeiger2;
zeiger = &variable; /* mit Adressoperator */
zeiger = feld; /* mit Feld */
zeiger = zeiger2; /* mit weiterem Zeiger */
zeiger = NULL; /* mit NULL */
Adressoperator
Die Adresse einer Variablen kann vom Programmierer zwar nicht bestimmt, aber über den unären Adressoperator &
ermittelt werden. Die Adresse eines Datenobjektes ist über die gesamte Laufzeit des Programms unveränderlich. Der Adressoperator kann auf alle Datenobjekte mit Ausnahme von Bitfeldern und Datenobjekten der Speicherklasse register
angewendet werden.[1]
int variable = 0; /* Definition einer Variable vom Typ int und Initialisierung */
int *zeiger = &variable; /* Definition einer Zeigervariablen und Initialisierung */
printf("%p", (void*)&variable); /* gibt Adresse der Variablen in einer implementierungsabhängigen Darstellung aus, z. B. als Hexadezimalzahl */
Inhaltsoperator
Hat man eine Adresse zur Verfügung, kann man mithilfe des Inhaltsoperators *
auf den Wert, der an dieser Adresse gespeichert ist, zugreifen. Der Stern in der Definition eines Zeigers *
ist nicht der Inhaltsoperator *
, es wird also dasselbe Symbol für unterschiedliche Zwecke verwendet. Bei der Verwendung des Inhaltsoperators auf einen Zeiger spricht man auch von der Dereferenzierung des Zeigers.
int variable = 3;
int *zeiger = &variable; /* hier handelt es sich nicht um den Inhaltsoperator */
printf("%d", *&variable); /* gibt den Wert „3“ aus */
printf("%d", *zeiger); /* gibt den Wert „3“ aus */
* zeiger = 5;
printf("%d", *zeiger); /* gibt den Wert „5“ aus */
* zeiger = *zeiger + 1;
printf("%d", *zeiger); /* gibt den Wert „6“ aus */
Nullzeiger
Soll ein Zeiger auf kein Objekt zeigen, kann man ihm den Wert NULL
zuweisen. Der Zeiger ist damit ungültig und kann solange ihm keine gültige Adresse zugewiesen wird, auch nicht sinnvoll genutzt werden, eine Dereferenzierung führt meistens zu einem Laufzeitfehler nebst Programmabbruch.
Sinnvoll hingegen ist der Vergleich von (Daten)Objektzeigern mit NULL
, was auch die Hauptanwendung ist.
int *zeiger;
zeiger = NULL;
* zeiger = 0; /* Fehler */
zeiger = malloc( sizeof(*zeiger) );
if( zeiger == NULL )
puts("Fehler bei Speicherreservierung");
NULL
ist ein Macro und wird in mehreren Header-Dateien definiert (mindestens in stddef.h
). Die Definition ist vom Standard implementierungsabhängig vorgegeben und vom Compilerhersteller passend implementiert, z. B.
#define NULL 0
#define NULL 0L
#define NULL (void *) 0
Zeigerarithmetik
Zeiger erhöhen und vermindern
Zeiger können nur mithilfe der Rechenoperationen Addition einer Ganzzahl und Subtraktion einer Ganzzahl verändert werden. Bei der Addition einer Ganzzahl wird die vom Zeiger gespeicherte Adresse entsprechend erhöht, bei der Subtraktion vermindert.
char zeichen;
char *zeiger = &zeichen;
printf("%p\n", zeiger); /* gibt z. B. die Adresse „0019FF01“ aus */
zeiger = zeiger + 1;
printf("%p\n", zeiger); /* gibt dann die Adresse „0019FF02“ aus */
Die Summe einer in einem Zeiger gespeicherten Adresse und einer Ganzzahl ergibt in C allerdings nur bei einem Zeiger auf einen Character die um die Ganzzahl erhöhte Adresse. Vor der Addition wird die Ganzzahl nämlich noch mit der Speichergröße des Datentyps multipliziert, auf den der Zeiger verweist. Wenn ein Integer vier Bytes benötigt, ergibt die Addition einer Adresse, die auf einen Integer zeigt, mit der Ganzzahl Eins, die um vier Ganzzahlen höhere Adresse. Mit der Addition und Subtraktion auf Zeigern kann also bequem um ein oder mehr Werte nach vorn bzw. hinten gesprungen werden.
int zahl;
int *zeiger = &zahl;
printf("%p\n", zeiger); /* gibt z. B. die Adresse „0019FF01“ aus */
zeiger = zeiger + 2;
printf("%p\n", zeiger); /* gibt dann „0019FF09“ aus, also 0019FF01 plus 4 mal 2 */
Addition und Subtraktion können wie in C allgemein üblich auch hier verkürzt angeschrieben werden.
zeiger += 5;
zeiger++;
Vergleiche von Zeigern
Um Zeiger zu vergleichen, können die Vergleichsoperationen und verwendet werden.
Differenz von Zeigern
Um festzuhalten, wie viele Bytes zwei Zeiger auseinanderliegen, steht der in der Header-Datei stddef.h
definierte primitive Datentyp ptrdiff_t
zur Verfügung. Er wird verwendet, um das Ergebnis einer Subtraktion von zwei Zeigern zu speichern, deren Ergebnis gleichbedeutend mit der Entfernung der beiden Zeiger ist.
char *zeichenkette = "hallo";
char *zeiger = &zeichenkette;
char *zeiger2 = zeiger + 2;
ptrdiff_t differenz;
differenz = zeiger2 - zeiger;
printf("%d\n", differenz); /* gibt die Differenz in Bytes aus, hier „2“ */
Zeiger und Felder
Zeiger und Felder können vom Programmierer in vielen Fällen mit genau derselben Syntax verwendet werden, jedoch nicht in allen.
int *zeiger;
int feld[] = { 1, 2, 3 };
zeiger = feld;
/* Zugriff auf die in einem Zeiger bzw. einem Feld gespeicherte Adresse */
printf("%p\n", zeiger); /* gibt beispielsweise „0019FEEC“ aus */
printf("%p\n", feld); /* gibt dann ebenfalls „0019FEEC“ aus */
/* Zugriff auf die Adresse eines Elements eines Feldes */
printf("%p\n", &zeiger[1]); /* gibt dann ebenfalls „0019FEF0“ aus */
printf("%p\n", &feld[1]); /* gibt dann ebenfalls „0019FEF0“ aus */
printf("%p\n", zeiger + 1); /* gibt dann ebenfalls „0019FEF0“ aus */
printf("%p\n", feld + 1); /* gibt dann ebenfalls „0019FEF0“ aus */
/* Zugriff auf den Wert eines Elements eines Feldes */
printf("%d\n", zeiger[1]); /* gibt „2“ aus */
printf("%d\n", feld[1]); /* gibt „2“ aus */
printf("%d\n", *(zeiger + 1)); /* gibt „2“ aus */
printf("%d\n", *(feld + 1)); /* gibt „2“ aus */
Das Arbeiten mit Zeigern unterscheidet sich vom Arbeiten mit Feldern insofern, als die Adresse eines Feldes konstant und deshalb nicht veränderbar ist.
int *zeiger;
int feld[3];
zeiger = feld; /* „feld = zeiger;“ ergäbe hingegen einen Fehler */
zeiger++; /* „feld++;“ ergäbe hingegen einen Fehler */
/* auch hat der Zeiger selbst eine andere Adresse als das Feld */
printf("%p: %p\n", &zeiger, zeiger); /* ergibt beispielsweise „0019FEF8: 0019FEE4“ */
printf("%p: %p\n", &feld, feld); /* ergibt dann „0019FEE4: 0019FEE4“ */
Wird ein Feld an eine Funktion übergeben, so wird es unabhängig von der Funktionsdeklaration immer in einen Zeiger auf sein erstes Element umgewandelt.
void funktion(int feld[]);
void funktion(int *feld); /* gleichbedeutend wie obige Deklaration */
Zeiger auf Zeiger
Ein Zeiger kann auf Objekte von beliebigem Datentyp zeigen, also auch auf Zeiger selbst. Dies lässt sich endlos fortsetzen, mit Zeigern, die auf Zeiger zeigen, die auf Zeiger zeigen usw. In der Praxis kommen Zeiger auf Zeiger durchaus vor, bereits sehr selten auch noch Zeiger auf Zeiger, die auf Zeiger zeigen.
int zahl = 3;
int *zeiger = &zahl; /* Zeiger auf Objekt vom Typ Integer */
int **zeiger2 = &zeiger; /* Zeiger auf Zeiger auf Objekt vom Typ Integer */
Zeiger auf Zeichenketten
Eine Zeichenkette ist immer ein Feld, dessen Feldelemente Zeichen sind. Mit einem Zeiger kann man auf den Anfang einer Zeichenkette zeigen. Zeiger auf Zeichenketten werden häufig genutzt, beispielsweise bei der Übergabe von Zeichenketten an Funktionen.
char feld[] = "Hallo";
char *zeiger = "Welt!"; /* „zeiger“ zeigt auf ein anonymes Array */
feld[3] = 'Z'; /* das Ergebnis von „zeiger[3] = 'Z';“ ist im Standard hingegen nicht definiert*/
zeiger = feld; /* „feld = zeiger;“ ist hingegen nicht möglich */
printf("%s - %zu\n", feld, sizeof(feld)); /* gibt z. B. „HalZo - 6“ aus */
printf("%s - %zu\n", zeiger, sizeof(zeiger)); /* gibt z. B. „HalZo - 4“ aus */
Anwendungsgebiete
Verwaltung von dynamischem Speicher
Dynamischer Speicher findet in C im Gegensatz zu automatischem oder statischem Speicher Verwendung in Situationen, in denen erst zur Laufzeit die erforderliche Größe bekannt ist oder die gewünschte Größe die Grenzen des automatischen/statischen Speichers im Prozess überschreitet.
Um während der Laufzeit dynamischen Speicher für das Programm anzufordern, muss in C die Funktion malloc
(oder die zu ihr ähnliche Funktionen calloc, realloc
) verwendet werden. malloc
reserviert den benötigten Speicher und liefert einen Zeiger auf diesen zurück. Ebenso muss in C die Freigabe dieses Speichers nach Gebrauch manuell mit der Funktion free
erfolgen.
Um in C mit dynamischem Speicher zu arbeiten, ist es also unumgänglich, auch mit Zeigern zu arbeiten.
int *zeiger = malloc(sizeof(int)); /* Zeiger auf den Beginn eines zur Laufzeit reservierten Speicherbereiches, hier Speicher für genau einen int-Wert */
... Verwendung von zeiger ...
zeiger[0] = 1;
printf("%d", zeiger[0]);
* zeiger = 2;
printf("%d", *zeiger);
free(zeiger); /* Freigeben des Speichers */
... nach free() bedeutet die Verwendung von zeiger undefiniertes Verhalten (UB) ...
Beim Freigeben von dynamischem Speicher mit free()
ist darauf zu achten, dass der Zeigerwert (d. h. die gespeicherte Adresse) exakt die gleiche ist wie zum Zeitpunkt von malloc
und dass free
nur genau einmal mit dieser Adresse aufgerufen wird:
int *zeiger = malloc( 10 * sizeof(int));
... Verwendung von zeiger ...
for( int i=0; i<10; i++ ) zeiger[i] = i;
for( int i=0; i<10; i++ ) printf("%d", zeiger[i]);
free(zeiger); /* Freigeben des Speichers */
free(zeiger); /* Fehler! */
int *zeiger = malloc( 10 * sizeof(int));
zeiger++;
free(zeiger); /* Fehler! */
Zeiger als Funktionsparameter
Für Funktionsparameter, die nicht als Zeiger übergeben werden (call by value), wird innerhalb der Funktion eine Kopie erzeugt. Ist eine Kopie nicht nötig oder unerwünscht, ist es auch möglich, Zeiger auf Datenobjekte an Funktionen zu übergeben (call by reference). Ein weiterer wichtiger Grund für die Übergabe von Zeigern an Funktionen ist der eingeschränkte Gültigkeitsbereich von Variablen. Es ist nur dann möglich, innerhalb von Funktionen Variablen aufrufender Programmblöcke zu verändern, wenn diese entweder global sind (was meist unerwünscht ist) oder der Funktion Zeiger auf diese Variablen übergeben werden.
void funktion(int *zeiger) {
*zeiger = 1; /* der Wert an der übergebenen Adresse wird „1“ mit überschrieben */
}
main() {
int ganzzahl = 0;
funktion(&ganzzahl); /* als Argument wird eine Adresse übergeben */
printf("%d\n", ganzzahl); /* gibt „1“ aus */
}
Die Parameterübergabe mithilfe von Zeigern ist auch die einzige Möglichkeit, um auch Funktionen als Argumente an andere Funktionen übergeben zu können.
Zeiger als Rückgabewert einer Funktion
Viele Funktionen der Standard-Bibliothek geben einen Zeiger als Rückgabewert zurück. Der zurückgegebene Zeiger ist dabei immer ein Zeiger auf die Anfangsadresse des Rückgabetyps. Anwendung findet diese Methode in C vor allem zur Übergabe von Zeichenketten und Strukturen.
int *funktion(void) {
static int zahl = 3;
int *zeiger = &zahl;
return zeiger; /* hier wird ein Zeiger zurückgegeben */
}
main() {
int *zeiger;
zeiger = funktion();
printf("%d\n", *zeiger); /* gibt „3“ aus */
}
Datenstrukturen
Siehe den Artikel Verbund (Datentyp).
Rekursive Datenstrukturen
Rekursive Datenstrukturen wie Listen oder Bäume sind kaum ohne Zeiger implementierbar.
Verarbeitung von Datenobjekten beliebigen Typs
Mithilfe des typenlosen void
-Zeigers lassen sich Datenobjekte beliebigen Typs verarbeiten.
Speichergröße von Zeigern
Die Speichergröße, die ein Zeiger benötigt, hängt von der Implementierung ab und beträgt in der Regel zwischen zwei und acht Bytes. Ermittelt wird die Größe mit der Funktion sizeof
. Da ein Zeiger immer nur eine Adresse speichert, ist es unerheblich, ob der Zeiger beispielsweise auf eine Integer- oder eine Double-Variable zeigt.
int *zeiger;
double *zeiger2;
printf("%zu\n", sizeof(zeiger)); /* gibt zum Beispiel „4“ aus */
printf("%zu\n", sizeof(zeiger2)); /* ergibt denselben Wert noch einmal, hier also wieder „4“ */
Typensicherung
Der Datentyp Zeiger ist in C streng typisiert. So ist beispielsweise die Zuweisung einer Adresse einer Double-Variablen an einen Zeiger vom Datentyp int *
zulässig, führt in der Regel jedoch nicht zu sinnvollen Ergebnissen. Daher sind Zeigervariablen in C nicht typsicher und nur im Sinne der Speicheradresse zuweisungskompatibel, nicht jedoch in Bezug auf die referenzierten Datentypen. Somit kann es bei der Programmierung leicht zu Typverletzungen kommen, die wiederum instabile oder fehlerhafte Programme verursachen können.[2]
Literatur
- Brian Kernighan, Dennis Ritchie: The C Programming Language. 2. Auflage, Prentice Hall, Englewood Cliffs (NJ) 1988, ISBN 0-13-110362-8, S. 93–126. (Deutsche Übersetzung: Brian Kernighan, Dennis Ritchie: Programmieren in C. Mit dem C-Reference Manual in deutscher Sprache. 2. Auflage, Hanser, München/Wien 1990, ISBN 3-446-15497-3).
Einzelnachweise
- ↑ Brian Kernighan, Dennis Ritchie: The C Programming Language. 2. Auflage, Prentice Hall, Englewood Cliffs (NJ) 1988, ISBN 0-13-110362-8, S. 94.
- ↑ CSE 341: Unsafe languages (C), Computer Science washington.edu, abgerufen am 2. Dezember 2016.