Zeiger (C)Der Zeiger oder der Pointer in der Programmiersprache C ist eine Variable, in der eine Speicheradresse gespeichert wird. Solche Zeiger werden in C häufig eingesetzt. DefinitionDie Definition eines Zeigers besteht aus dem Datentyp des Zeigers und dem gewünschten Zeigernamen. Der Datentyp eines Zeigers besteht wiederum aus dem Datentyp des Wertes, auf den gezeigt wird, sowie aus einem Asterisk. Ein Datentyp eines Zeigers wäre also z. B. 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. ZuweisungenDie Zuweisung einer Adresse an einen Zeiger erfolgt mithilfe des Adressoperators, eines Feldes, eines weiteren Zeigers oder des Wertes von int variable = 0;
int feld[10];
int *zeiger;
int *zeiger2;
zeiger = &variable; /* mit Adressoperator */
zeiger = feld; /* mit Feld */
zeiger2 = zeiger; /* mit weiterem Zeiger */
zeiger = NULL; /* mit NULL */
AdressoperatorDie Adresse einer Variablen kann vom Programmierer zwar nicht festgelegt, aber über den unären Adressoperator 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 */
InhaltsoperatorHat man eine Adresse zur Verfügung, kann man mithilfe des Inhaltsoperators 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 */
NullzeigerSoll ein Zeiger auf kein Objekt zeigen, kann man ihm den Wert int *zeiger;
zeiger = NULL;
* zeiger = 0; /* Fehler */
zeiger = malloc( sizeof(*zeiger) );
if( zeiger == NULL )
puts("Fehler bei Speicherreservierung");
#define NULL 0
#define NULL 0L
#define NULL (void *) 0
ZeigerarithmetikZeiger erhöhen und vermindernZeiger 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 ZeigernUm Zeiger zu vergleichen, können die Vergleichsoperationen Die Größer-/Kleiner-Vergleiche sind jedoch nur für Elemente des gleichen Arrays definiert. Für verschiedene Objekte, die nicht Elemente des gleichen Arrays sind, ist das Ergebnis "undefined behavior". Differenz von ZeigernUm zu ermitteln, wie viele Elemente zwei Zeiger auseinanderliegen, steht der in der Header-Datei int array[] = {10,20,30,40};
int* zeiger = &array;
int* zeiger2 = array + 2;
ptrdiff_t differenz = zeiger2 - zeiger;
printf("%d\n", differenz); // gibt die Entfernung der Elemente an, hier „2“ */
Auch diese Operation ist nur für Elemente des gleichen Arrays definiert. Zeiger und FelderZeiger 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 ZeigerEin 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 ZeichenkettenEine 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 */
AnwendungsgebieteVerwaltung von dynamischem SpeicherDynamischer 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 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 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 FunktionsparameterFü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 mit „1“ ü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 FunktionViele 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 */
}
DatenstrukturenSiehe den Artikel Verbund (Datentyp). Rekursive DatenstrukturenRekursive Datenstrukturen wie Listen oder Bäume sind kaum ohne Zeiger implementierbar. Verarbeitung von Datenobjekten beliebigen TypsMithilfe des typenlosen Speichergröße von ZeigernDie 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 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“ */
TypensicherungZeigervariablen sind in C nicht typsicher und nur im Sinne der Speicheradresse zuweisungskompatibel, nicht jedoch in Bezug auf die referenzierten Datentypen. So ist beispielsweise die Zuweisung einer Adresse einer Double-Variablen an einen Zeiger vom Datentyp Literatur
Einzelnachweise
|
Portal di Ensiklopedia Dunia