C Language
Zeiger
Suche…
Einführung
Ein Zeiger ist ein Variablentyp, der die Adresse eines anderen Objekts oder einer Funktion speichern kann.
Syntax
- <Datentyp> * <Variablenname>;
- int * ptrToInt;
- void * ptrToVoid; / * C89 + * /
- struct someStruct * ptrToStruct;
- int ** ptrToPtrToInt;
- int arr [Länge]; int * ptrToFirstElem = arr; / * Für <C99 muss 'length' eine Kompilierungszeitkonstante sein, für> = C11 muss es möglicherweise eine sein. * /
- int * arrayOfPtrsToInt [Länge]; / * Für <C99 muss 'length' eine Kompilierungszeitkonstante sein, für> = C11 muss es möglicherweise eine sein. * /
Bemerkungen
Die Position des Sterns hat keinen Einfluss auf die Bedeutung der Definition:
/* The * operator binds to right and therefore these are all equivalent. */
int *i;
int * i;
int* i;
Wenn Sie jedoch mehrere Zeiger gleichzeitig definieren, benötigt jeder einen eigenen Stern:
int *i, *j; /* i and j are both pointers */
int* i, j; /* i is a pointer, but j is an int not a pointer variable */
Ein Array von Zeigern ist ebenfalls möglich, wobei ein Sternchen vor dem Namen der Array-Variablen steht:
int *foo[2]; /* foo is a array of pointers, can be accessed as *foo[0] and *foo[1] */
Häufige Fehler
Die unsachgemäße Verwendung von Zeigern ist häufig eine Fehlerquelle, die Sicherheitsfehler oder Programmabstürze beinhalten kann, meistens aufgrund von Segmentierungsfehlern.
Nicht auf Zuordnungsfehler prüfen
Es kann nicht garantiert werden, dass die Speicherzuweisung erfolgreich ist, und kann stattdessen einen NULL
Zeiger zurückgeben. Wenn Sie den zurückgegebenen Wert verwenden, ohne zu überprüfen, ob die Zuordnung erfolgreich ist, wird ein undefiniertes Verhalten aufgerufen. Dies führt in der Regel zu einem Absturz, es gibt jedoch keine Garantie dafür, dass es zu einem Absturz kommt. Wenn Sie sich darauf verlassen, kann dies ebenfalls zu Problemen führen.
Zum Beispiel unsichere Weise:
struct SomeStruct *s = malloc(sizeof *s);
s->someValue = 0; /* UNSAFE, because s might be a null pointer */
Sicherer Weg:
struct SomeStruct *s = malloc(sizeof *s);
if (s)
{
s->someValue = 0; /* This is safe, we have checked that s is valid */
}
Verwenden Sie beim Anfordern von Speicher Literalzahlen anstelle von sizeof
Für eine bestimmte Compiler- / Maschinenkonfiguration haben Typen eine bekannte Größe. Es gibt jedoch keinen Standard, der definiert, dass die Größe eines bestimmten Typs (mit Ausnahme von char
) für alle Compiler- / Maschinenkonfigurationen gleich ist. Wenn der Code 4 anstelle von sizeof(int)
für die Speicherzuweisung verwendet, funktioniert er möglicherweise auf dem ursprünglichen Computer, der Code muss jedoch nicht unbedingt auf andere Computer oder Compiler übertragen werden. Feste Größen für Typen sollten durch sizeof(that_type)
oder sizeof(*var_ptr_to_that_type)
.
Nicht portierbare Zuteilung:
int *intPtr = malloc(4*1000); /* allocating storage for 1000 int */
long *longPtr = malloc(8*1000); /* allocating storage for 1000 long */
Portable Zuordnung:
int *intPtr = malloc(sizeof(int)*1000); /* allocating storage for 1000 int */
long *longPtr = malloc(sizeof(long)*1000); /* allocating storage for 1000 long */
Oder noch besser:
int *intPtr = malloc(sizeof(*intPtr)*1000); /* allocating storage for 1000 int */
long *longPtr = malloc(sizeof(*longPtr)*1000); /* allocating storage for 1000 long */
Speicherlecks
Wenn die Zuordnung des Speichers über free
nicht mehr erfolgt, führt dies zu einem nicht wiederverwendbaren Speicher, der vom Programm nicht mehr verwendet wird. Dies wird als Speicherleck bezeichnet . Speicherverluste verschwenden Speicherressourcen und können zu Zuordnungsfehlern führen.
Logische Fehler
Alle Zuordnungen müssen demselben Muster folgen:
- Zuweisung unter Verwendung
malloc
(odercalloc
) - Verwendung zum Speichern von Daten
- Aufteilung mit
free
Wird auf diese Muster zu halten, wie Speicher nach einem Aufruf mit free
( baumelt Zeiger ) oder vor einem Aufruf von malloc
( Wild Zeigern ), rufe free
zweimal ( „Double Free“) etc., in der Regel verursacht einen Segmentierungsfehler und führt zum Absturz des Programms.
Diese Fehler können vorübergehend und schwer zu debuggen sein. Zum Beispiel wird freigegebener Speicher normalerweise nicht sofort vom Betriebssystem zurückgefordert. Daher können baumelnde Zeiger für eine Weile bestehen bleiben und scheinen zu funktionieren.
In Systemen, in denen es funktioniert, ist Valgrind ein unschätzbares Werkzeug, um zu ermitteln, welcher Speicher verloren geht und wo er ursprünglich zugewiesen wurde.
Erstellen von Zeigern zum Stapeln von Variablen
Durch das Erstellen eines Zeigers wird die Lebensdauer der Variablen, auf die gezeigt wird, nicht verlängert. Zum Beispiel:
int* myFunction()
{
int x = 10;
return &x;
}
Hier hat x
eine automatische Speicherdauer (allgemein als Stapelzuordnung bezeichnet ). Da es auf dem Stack zugewiesen wird, ist seine Lebensdauer nur so lange, wie myFunction
ausgeführt wird. Nachdem myFunction
wurde, wird die Variable x
zerstört. Diese Funktion ruft die Adresse von x
(mit &x
) ab und gibt sie an den Aufrufer zurück. Der Aufrufer erhält einen Zeiger auf eine nicht vorhandene Variable. Wenn Sie versuchen, auf diese Variable zuzugreifen, wird ein undefiniertes Verhalten ausgelöst.
Die meisten Compiler löschen einen Stack-Frame nach dem Beenden der Funktion nicht wirklich. Daher liefert dereferenzierte Zeiger oft die erwarteten Daten. Wenn jedoch eine andere Funktion aufgerufen wird, kann der Speicher, auf den gezeigt wird, überschrieben werden, und es scheint, dass die Daten, auf die gezeigt wird, beschädigt wurden.
Um dies zu beheben, malloc
den Speicher für die zurückzugebende Variable malloc
und einen Zeiger auf den neu erstellten Speicher zurückgeben oder die Übergabe eines gültigen Zeigers an die Funktion, anstatt einen zu übergeben, z. B .:
#include <stdlib.h>
#include <stdio.h>
int *solution1(void)
{
int *x = malloc(sizeof *x);
if (x == NULL)
{
/* Something went wrong */
return NULL;
}
*x = 10;
return x;
}
void solution2(int *x)
{
/* NB: calling this function with an invalid or null pointer
causes undefined behaviour. */
*x = 10;
}
int main(void)
{
{
/* Use solution1() */
int *foo = solution1();
if (foo == NULL)
{
/* Something went wrong */
return 1;
}
printf("The value set by solution1() is %i\n", *foo);
/* Will output: "The value set by solution1() is 10" */
free(foo); /* Tidy up */
}
{
/* Use solution2() */
int bar;
solution2(&bar);
printf("The value set by solution2() is %i\n", bar);
/* Will output: "The value set by solution2() is 10" */
}
return 0;
}
Inkrementieren / Dekrementieren und Dereferenzieren
Wenn Sie *p++
schreiben, um zu erhöhen, was von p
angezeigt wird, sind Sie falsch.
Nach dem Inkrementieren / Dekrementieren wird vor der Dereferenzierung ausgeführt. Daher wird dieser Ausdruck den Zeiger p
selbst inkrementieren und den Wert von p
bevor er inkrementiert wird, ohne ihn zu ändern.
Sie sollten (*p)++
schreiben, um zu erhöhen, was von p
angezeigt wird.
Diese Regel gilt auch für das *p--
: *p--
dekrementiert den Zeiger p
selbst, nicht das, was von p
*p--
wird.
Dereferenzieren eines Zeigers
int a = 1;
int *a_pointer = &a;
Um a_pointer
zu dereferenzieren und den Wert von a zu ändern, verwenden wir die folgende Operation
*a_pointer = 2;
Dies kann mit den folgenden Druckanweisungen überprüft werden.
printf("%d\n", a); /* Prints 2 */
printf("%d\n", *a_pointer); /* Also prints 2 */
Es wäre jedoch falsch, einen NULL
Zeiger oder einen anderen ungültigen Zeiger dereferenzieren zu wollen. Diese
int *p1, *p2;
p1 = (int *) 0xbad;
p2 = NULL;
*p1 = 42;
*p2 = *p1 + 1;
ist normalerweise undefiniertes Verhalten . p1
darf nicht dereferenziert werden, da er auf eine Adresse 0xbad
verweist, die möglicherweise keine gültige Adresse ist. Wer weiß was da ist Es kann sich um den Speicher des Betriebssystems oder um den Speicher eines anderen Programms handeln. Der einzige Zeitcode wie dieser wird in der Embedded-Entwicklung verwendet, in der bestimmte Informationen an hart codierten Adressen gespeichert werden. p2
kann nicht dereferenziert werden, da es NULL
, was ungültig ist.
Dereferenzieren eines Zeigers auf eine Struktur
Nehmen wir an, wir haben folgende Struktur:
struct MY_STRUCT
{
int my_int;
float my_float;
};
Wir können MY_STRUCT
so definieren, dass das Schlüsselwort struct
sodass wir nicht jedes Mal die struct MY_STRUCT
müssen. Dies ist jedoch optional.
typedef struct MY_STRUCT MY_STRUCT;
Wenn wir dann einen Zeiger auf eine Instanz dieser Struktur haben
MY_STRUCT *instance;
Wenn diese Anweisung im Dateibereich angezeigt wird, wird die instance
beim Programmstart mit einem Nullzeiger initialisiert. Wenn diese Anweisung in einer Funktion erscheint, ist ihr Wert nicht definiert. Die Variable muss so initialisiert werden, dass sie auf eine gültige MY_STRUCT
Variable oder auf einen dynamisch zugewiesenen Speicherplatz MY_STRUCT
, bevor sie dereferenziert werden kann. Zum Beispiel:
MY_STRUCT info = { 1, 3.141593F };
MY_STRUCT *instance = &info;
Wenn der Zeiger gültig ist, können Sie ihn mit einer von zwei verschiedenen Schreibweisen auf seine Mitglieder zugreifen:
int a = (*instance).my_int;
float b = instance->my_float;
Obwohl beide Methoden funktionieren, ist es besser, den Operator arrow ->
anstelle der Kombination von Klammern, des dereference *
Operators und des Punktes zu verwenden .
Operator, weil es einfacher zu lesen und zu verstehen ist, insbesondere bei verschachtelten Anwendungen.
Ein weiterer wichtiger Unterschied wird unten gezeigt:
MY_STRUCT copy = *instance;
copy.my_int = 2;
In diesem Fall enthält die copy
eine Kopie des Inhalts der instance
. Das my_int
von my_int
einer copy
ändert dies nicht in der instance
.
MY_STRUCT *ref = instance;
ref->my_int = 2;
In diesem Fall verweist ref
eine instance
. my_int
mithilfe der Referenz ändern, wird dies in der instance
geändert.
Es ist allgemein üblich, Zeiger auf Strukturen als Parameter in Funktionen und nicht auf die Struktur selbst zu verwenden. Die Verwendung der Struktur als Funktionsparameter kann dazu führen, dass der Stapel überläuft, wenn die Struktur groß ist. Die Verwendung eines Zeigers auf eine Struktur beansprucht nur genügend Stapelspeicherplatz für den Zeiger, kann jedoch Nebenwirkungen verursachen, wenn die Funktion die Struktur ändert, die an die Funktion übergeben wird.
Funktionszeiger
Zeiger können auch zum Zeigen auf Funktionen verwendet werden.
Nehmen wir eine grundlegende Funktion:
int my_function(int a, int b) { return 2 * a + 3 * b; }
Nun definieren wir einen Zeiger des Typs dieser Funktion:
int (*my_pointer)(int, int);
Um eine zu erstellen, verwenden Sie einfach diese Vorlage:
return_type_of_func (*my_func_pointer)(type_arg1, type_arg2, ...)
Wir müssen diesen Zeiger dann der Funktion zuordnen:
my_pointer = &my_function;
Mit diesem Zeiger kann jetzt die Funktion aufgerufen werden:
/* Calling the pointed function */ int result = (*my_pointer)(4, 2); ... /* Using the function pointer as an argument to another function */ void another_function(int (*another_pointer)(int, int)) { int a = 4; int b = 2; int result = (*another_pointer)(a, b); printf("%d\n", result); }
Obwohl diese Syntax mit Basistypen natürlicher und kohärenter erscheint, erfordern das Zuordnen und Dereferenzieren von Funktionszeigern keine Verwendung der Operatoren &
und *
. Der folgende Ausschnitt ist also gleichermaßen gültig:
/* Attribution without the & operator */ my_pointer = my_function; /* Dereferencing without the * operator */ int result = my_pointer(4, 2);
Um die Lesbarkeit von Funktionszeigern zu erhöhen, können Typedefs verwendet werden.
typedef void (*Callback)(int a); void some_function(Callback callback) { int a = 4; callback(a); }
Ein weiterer Lesbarkeitstrick besteht darin, dass der C-Standard einen Funktionszeiger in Argumenten wie oben (aber nicht in der Variablendeklaration) auf etwas vereinfacht, das wie ein Funktionsprototyp aussieht. Daher kann Folgendes für Funktionsdefinitionen und Deklarationen gleichwertig verwendet werden:
void some_function(void callback(int))
{
int a = 4;
callback(a);
}
Siehe auch
Zeiger initialisieren
Die Zeigerinitialisierung ist eine gute Möglichkeit, wilde Zeiger zu vermeiden. Die Initialisierung ist einfach und unterscheidet sich nicht von der Initialisierung einer Variablen.
#include <stddef.h>
int main()
{
int *p1 = NULL;
char *p2 = NULL;
float *p3 = NULL;
/* NULL is a macro defined in stddef.h, stdio.h, stdlib.h, and string.h */
...
}
Bei den meisten Betriebssystemen führt das versehentliche Verwenden eines Zeigers, der auf NULL
initialisiert wurde, häufig zum sofortigen Absturz des Programms, wodurch die Ursache des Problems leicht ermittelt werden kann. Die Verwendung eines nicht initialisierten Zeigers kann häufig zu schwer diagnostizierbaren Fehlern führen.
Vorsicht:
Das Ergebnis der Dereferenzierung eines NULL
Zeigers ist undefiniert, sodass es nicht unbedingt zu einem Absturz führt, selbst wenn dies das natürliche Verhalten des Betriebssystems ist, auf dem das Programm ausgeführt wird. Compiler-Optimierungen können den Absturz maskieren, dazu führen, dass der Absturz vor oder nach dem Punkt im Quellcode auftritt, an dem die Nullzeiger-Dereferenzierung aufgetreten ist, oder Teile des Codes, der die Nullzeiger-Dereferenzierung enthält, unerwartet aus dem Programm entfernt werden. Debug-Builds weisen diese Verhaltensweisen normalerweise nicht auf, dies wird jedoch durch den Sprachstandard nicht garantiert. Andere unerwartete und / oder unerwünschte Verhaltensweisen sind ebenfalls zulässig.
Da NULL
niemals auf eine Variable, auf zugewiesenen Speicher oder auf eine Funktion verweist, ist die Verwendung als Guard-Wert sicher.
Vorsicht:
Normalerweise ist NULL
als (void *)0
. Dies bedeutet jedoch nicht, dass die zugewiesene Speicheradresse 0x0
. Weitere Informationen finden Sie unter C-faq für NULL-Zeiger
Beachten Sie, dass Sie Zeiger auch so initialisieren können, dass sie andere Werte als NULL enthalten.
int i1;
int main()
{
int *p1 = &i1;
const char *p2 = "A constant string to point to";
float *p3 = malloc(10 * sizeof(float));
}
Adresse des Betreibers (&)
Für jedes Objekt (dh Variable, Array, Union, Struktur, Zeiger oder Funktion) kann der unäre Adressoperator verwendet werden, um auf die Adresse dieses Objekts zuzugreifen.
Nehme an, dass
int i = 1;
int *p = NULL;
Also dann eine Aussage p = &i;
kopiert die Adresse der Variablen i
in den Zeiger p
.
Es wird als p
Punkte auf i
ausgedrückt.
printf("%d\n", *p);
gibt 1 aus, der Wert von i
.
Zeigerarithmetik
Bitte sehen Sie hier: Pointer Arithmetic
void * -Punkte als Argumente und geben Werte an Standardfunktionen zurück
void*
ist ein catch-All-Typ für Zeiger auf Objekttypen. Ein Beispiel dafür ist die malloc
Funktion, die als deklariert ist
void* malloc(size_t);
Der Rückgabetyp Zeiger auf ungültig bedeutet, dass der Rückgabewert von malloc
einem Zeiger auf einen anderen Objekttyp zugewiesen werden kann:
int* vector = malloc(10 * sizeof *vector);
Es wird allgemein als gute Praxis angesehen, die Werte nicht explizit in leere Zeiger zu verschieben und aus ihnen herauszugeben. In bestimmten Fällen von malloc()
dies darauf zurückzuführen, dass der Compiler bei einer expliziten Umwandlung einen falschen Rückgabetyp für malloc()
annehmen kann, aber nicht warnt, wenn Sie vergessen, stdlib.h
. Es ist auch der Fall, dass das richtige Verhalten von Leerzeiger verwendet wird, um sich besser dem DRY-Prinzip (nicht sich selbst wiederholen) anzupassen; Vergleichen Sie das obige mit dem folgenden, wobei der folgende Code mehrere unnötige zusätzliche Stellen enthält, an denen ein Tippfehler Probleme verursachen kann:
int* vector = (int*)malloc(10 * sizeof int*);
Ebenso Funktionen wie
void* memcpy(void *restrict target, void const *restrict source, size_t size);
haben ihre Argumente als void *
da die Adresse eines Objekts unabhängig vom Typ übergeben werden kann. Auch hier sollte ein Aufruf keine Umwandlung verwenden
unsigned char buffer[sizeof(int)];
int b = 67;
memcpy(buffer, &b, sizeof buffer);
Const-Zeiger
Einzelzeiger
Zeiger auf ein
int
Der Zeiger kann auf verschiedene Ganzzahlen zeigen und die
int
können durch den Zeiger geändert werden. Dieses Codebeispiel weist b zu, um aufint b
b zu zeigen, und ändert dann den Wert vonb
in100
.int b; int* p; p = &b; /* OK */ *p = 100; /* OK */
Zeiger auf eine
const int
Der Zeiger kann auf verschiedene ganze Zahlen zeigen, der Wert des
int
kann jedoch nicht über den Zeiger geändert werden.int b; const int* p; p = &b; /* OK */ *p = 100; /* Compiler Error */
const
Zeiger aufint
Der Zeiger kann nur auf ein
int
aber der Wert desint
kann durch den Zeiger geändert werden.int a, b; int* const p = &b; /* OK as initialisation, no assignment */ *p = 100; /* OK */ p = &a; /* Compiler Error */
const
Zeiger aufconst int
Der Zeiger kann nur auf ein
int
und dasint
kann nicht durch den Zeiger geändert werden.int a, b; const int* const p = &b; /* OK as initialisation, no assignment */ p = &a; /* Compiler Error */ *p = 100; /* Compiler Error */
Zeiger auf Zeiger
Zeiger auf einen Zeiger auf ein
int
Dieser Code weist dem Doppelzeiger
p
die Adresse vonp1
zu (der dann aufint* p1
(was aufint
)).Dann ändert
p1
aufint a
. Ändert dann den Wert von a auf 100.void f1(void) { int a, b; int *p1; int **p; p1 = &b; /* OK */ p = &p1; /* OK */ *p = &a; /* OK */ **p = 100; /* OK */ }
Zeiger auf Zeiger auf ein
const int
void f2(void) { int b; const int *p1; const int **p; p = &p1; /* OK */ *p = &b; /* OK */ **p = 100; /* error: assignment of read-only location ‘**p’ */ }
Zeiger auf
const
Zeiger auf einint
void f3(void) { int b; int *p1; int * const *p; p = &p1; /* OK */ *p = &b; /* error: assignment of read-only location ‘*p’ */ **p = 100; /* OK */ }
const
Zeiger auf Zeiger aufint
void f4(void) { int b; int *p1; int ** const p = &p1; /* OK as initialisation, not assignment */ p = &p1; /* error: assignment of read-only variable ‘p’ */ *p = &b; /* OK */ **p = 100; /* OK */ }
Zeiger auf
const
Zeiger aufconst int
void f5(void) { int b; const int *p1; const int * const *p; p = &p1; /* OK */ *p = &b; /* error: assignment of read-only location ‘*p’ */ **p = 100; /* error: assignment of read-only location ‘**p’ */ }
const
Zeiger aufconst int
void f6(void) { int b; const int *p1; const int ** const p = &p1; /* OK as initialisation, not assignment */ p = &p1; /* error: assignment of read-only variable ‘p’ */ *p = &b; /* OK */ **p = 100; /* error: assignment of read-only location ‘**p’ */ }
const
Zeiger aufconst
Zeiger aufint
void f7(void) { int b; int *p1; int * const * const p = &p1; /* OK as initialisation, not assignment */ p = &p1; /* error: assignment of read-only variable ‘p’ */ *p = &b; /* error: assignment of read-only location ‘*p’ */ **p = 100; /* OK */ }
Gleicher Stern, unterschiedliche Bedeutungen
Prämisse
Die verwirrendste Sache, die die Zeigersyntax in C und C ++ umgibt, besteht darin, dass es tatsächlich zwei verschiedene Bedeutungen gibt, wenn das Zeigersymbol, das Sternchen ( *
), mit einer Variablen verwendet wird.
Beispiel
Erstens verwenden Sie *
, um eine Zeigervariable zu deklarieren .
int i = 5;
/* 'p' is a pointer to an integer, initialized as NULL */
int *p = NULL;
/* '&i' evaluates into address of 'i', which then assigned to 'p' */
p = &i;
/* 'p' is now holding the address of 'i' */
Wenn Sie nicht deklarieren (oder multiplizieren), wird mit *
eine Zeigervariable dereferenziert :
*p = 123;
/* 'p' was pointing to 'i', so this changes value of 'i' to 123 */
Wenn Sie eine vorhandene Zeigervariable wollen Adresse von anderen Variablen zu halten, verwenden Sie nicht *
, aber es wie folgt tun:
p = &another_variable;
Eine häufige Verwirrung unter den C-Programmierern entsteht, wenn sie gleichzeitig eine Zeigervariable deklarieren und initialisieren.
int *p = &i;
Da int i = 5;
und int i; i = 5;
geben Sie das gleiche Ergebnis, einige von ihnen denken vielleicht int *p = &i;
und int *p; *p = &i;
gib auch das gleiche ergebnis. Tatsache ist, nein, int *p; *p = &i;
versucht, einen nicht initialisierten Zeiger vorzugeben, was zu UB führt. Verwenden Sie *
niemals, wenn Sie keinen Zeiger deklarieren oder dereferenzieren.
Fazit
Das Sternchen ( *
) hat zwei verschiedene Bedeutungen in Bezug auf Zeiger, abhängig davon, wo es verwendet wird. Bei Verwendung in einer Variablendeklaration sollte der Wert auf der rechten Seite der Gleichen Seite ein Zeigerwert auf eine Adresse im Speicher sein. Wenn der Stern mit einer bereits deklarierten Variablen verwendet wird , wird der Zeigerwert dereferenziert, der Zeigerwert wird an die angegebene Stelle im Speicher weitergeleitet, und der dort gespeicherte Wert kann zugewiesen oder abgerufen werden.
Wegbringen
Wenn Sie mit Zeigern arbeiten, müssen Sie sozusagen auf Ihre Ps und Qs achten. Denken Sie daran, wenn Sie das Sternchen verwenden und was es bedeutet, wenn Sie es dort verwenden. Wenn Sie dieses winzige Detail übersehen, kann dies zu fehlerhaftem und / oder undefiniertem Verhalten führen, mit dem Sie sich wirklich nicht auseinandersetzen müssen.
Zeiger auf Zeiger
In C kann ein Zeiger auf einen anderen Zeiger verweisen.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int A = 42;
int* pA = &A;
int** ppA = &pA;
int*** pppA = &ppA;
printf("%d", ***pppA); /* prints 42 */
return EXIT_SUCCESS;
}
Verweise und Verweise direkt sind jedoch nicht zulässig.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int A = 42;
int* pA = &A;
int** ppA = &&A; /* Compilation error here! */
int*** pppA = &&&A; /* Compilation error here! */
...
Einführung
Ein Zeiger wird wie jede andere Variable deklariert, mit der Ausnahme, dass ein Sternchen ( *
) zwischen dem Typ und dem Namen der Variablen steht, um anzuzeigen, dass es sich um einen Zeiger handelt.
int *pointer; /* inside a function, pointer is uninitialized and doesn't point to any valid object yet */
Um zwei Zeigervariablen desselben Typs zu deklarieren, verwenden Sie in derselben Deklaration das Sternchen vor jedem Bezeichner. Zum Beispiel,
int *iptr1, *iptr2;
int *iptr3, iptr4; /* iptr3 is a pointer variable, whereas iptr4 is misnamed and is an int */
Der durch ein kaufmännisches Und ( &
) gekennzeichnete Adressen- oder Referenzoperator gibt die Adresse einer gegebenen Variablen an, die in einem Zeiger eines geeigneten Typs platziert werden kann.
int value = 1;
pointer = &value;
Der Indirektions- oder Dereferenzierungsoperator, der durch ein Sternchen ( *
) gekennzeichnet ist, ruft den Inhalt eines Objekts ab, auf das ein Zeiger zeigt.
printf("Value of pointed to integer: %d\n", *pointer);
/* Value of pointed to integer: 1 */
Wenn der Zeiger auf eine Struktur oder einen Unionstyp zeigt, können Sie ihn dereferenzieren und direkt auf seine Mitglieder mit dem Operator ->
zugreifen:
SomeStruct *s = &someObject;
s->someMember = 5; /* Equivalent to (*s).someMember = 5 */
In C ist ein Zeiger ein bestimmter Werttyp, der erneut zugewiesen werden kann und ansonsten als eigenständige Variable behandelt wird. Im folgenden Beispiel wird beispielsweise der Wert des Zeigers (der Variablen) selbst gedruckt.
printf("Value of the pointer itself: %p\n", (void *)pointer);
/* Value of the pointer itself: 0x7ffcd41b06e4 */
/* This address will be different each time the program is executed */
Da ein Zeiger eine veränderliche Variable ist, kann er nicht auf ein gültiges Objekt zeigen, indem er entweder auf null gesetzt wird
pointer = 0; /* or alternatively */
pointer = NULL;
oder einfach durch das Enthalten eines beliebigen Bitmusters, das keine gültige Adresse ist. Letzteres ist eine sehr schlechte Situation, da es nicht getestet werden kann, bevor der Zeiger dereferenziert wird. Es gibt nur einen Test für den Fall, dass ein Zeiger null ist:
if (!pointer) exit(EXIT_FAILURE);
Ein Zeiger darf nur dereferenziert werden, wenn er auf ein gültiges Objekt zeigt. Andernfalls ist das Verhalten undefiniert. Viele moderne Implementierungen können Ihnen helfen, indem Sie Fehler wie Segmentierungsfehler auslösen und die Ausführung abbrechen. Andere können jedoch dazu führen, dass Ihr Programm in einem ungültigen Zustand bleibt.
Der vom Dereferenzierungsoperator zurückgegebene Wert ist ein veränderlicher Alias für die ursprüngliche Variable. Er kann also geändert werden, indem die ursprüngliche Variable geändert wird.
*pointer += 1;
printf("Value of pointed to variable after change: %d\n", *pointer);
/* Value of pointed to variable after change: 2 */
Zeiger sind auch neu zuweisbar. Dies bedeutet, dass ein Zeiger, der auf ein Objekt zeigt, später verwendet werden kann, um auf ein anderes Objekt desselben Typs zu zeigen.
int value2 = 10;
pointer = &value2;
printf("Value from pointer: %d\n", *pointer);
/* Value from pointer: 10 */
Zeiger haben wie jede andere Variable einen bestimmten Typ. Sie können die Adresse eines short int
einem long int
zuordnen. Ein solches Verhalten wird als Typ Punning bezeichnet und ist in C verboten, es gibt jedoch einige Ausnahmen.
Obwohl der Zeiger von einem bestimmten Typ sein muss, entspricht der für jeden Zeigertyp zugewiesene Speicher dem Speicher, der von der Umgebung zum Speichern von Adressen verwendet wird, und nicht der Größe des Typs, auf den der Zeiger verweist.
#include <stdio.h>
int main(void) {
printf("Size of int pointer: %zu\n", sizeof (int*)); /* size 4 bytes */
printf("Size of int variable: %zu\n", sizeof (int)); /* size 4 bytes */
printf("Size of char pointer: %zu\n", sizeof (char*)); /* size 4 bytes */
printf("Size of char variable: %zu\n", sizeof (char)); /* size 1 bytes */
printf("Size of short pointer: %zu\n", sizeof (short*)); /* size 4 bytes */
printf("Size of short variable: %zu\n", sizeof (short)); /* size 2 bytes */
return 0;
}
(Hinweis: Wenn Sie Microsoft Visual Studio verwenden, das die Standards C99 oder C11 nicht unterstützt, müssen Sie im obigen Beispiel %Iu
1 anstelle von %zu
.)
Beachten Sie, dass die obigen Ergebnisse in Zahlen von Umgebung zu Umgebung variieren können, aber alle Umgebungen würden für unterschiedliche Zeigertypen die gleiche Größe aufweisen.
Auszug basierend auf Informationen der Cardiff University C Pointers Introduction
Zeiger und Arrays
Zeiger und Arrays sind in C eng miteinander verbunden. Arrays in C werden immer an zusammenhängenden Stellen im Speicher gehalten. Die Zeigerarithmetik wird immer durch die Größe des Elements skaliert, auf das gezeigt wird. Wenn wir also ein Array von drei Doubles und einen Zeiger auf die Basis haben, bezieht sich *ptr
auf das erste Double, *(ptr + 1)
auf das zweite, *(ptr + 2)
auf das dritte. Eine bequemere Notation ist die Verwendung der Array-Notation []
.
double point[3] = {0.0, 1.0, 2.0};
double *ptr = point;
/* prints x 0.0, y 1.0 z 2.0 */
printf("x %f y %f z %f\n", ptr[0], ptr[1], ptr[2]);
Daher sind im Wesentlichen ptr und der Arrayname austauschbar. Diese Regel bedeutet auch, dass ein Array bei der Übergabe an ein Unterprogramm in einen Zeiger zerfällt.
double point[3] = {0.0, 1.0, 2.0};
printf("length of point is %s\n", length(point));
/* get the distance of a 3D point from the origin */
double length(double *pt)
{
return sqrt(pt[0] * pt[0] + pt[1] * pt[1] + pt[2] * pt[2])
}
Ein Zeiger kann auf ein beliebiges Element in einem Array oder auf das Element hinter dem letzten Element zeigen. Es ist jedoch ein Fehler, einen Zeiger auf einen anderen Wert einschließlich des Elements vor dem Array zu setzen. (Der Grund ist, dass bei segmentierten Architekturen die Adresse vor dem ersten Element eine Segmentgrenze überschreitet. Der Compiler stellt sicher, dass dies nicht für das letzte Element plus eins geschieht.)
Fußnote 1: Informationen zum Microsoft-Format finden Sie über die Syntax printf()
und Formatspezifikation .
Polymorphes Verhalten mit leeren Zeigern
Die Standardbibliotheksfunktion qsort()
ist ein gutes Beispiel dafür, wie man mithilfe von Leerzeigern eine einzelne Funktion auf eine Vielzahl unterschiedlicher Typen anwenden kann.
void qsort (
void *base, /* Array to be sorted */
size_t num, /* Number of elements in array */
size_t size, /* Size in bytes of each element */
int (*compar)(const void *, const void *)); /* Comparison function for two elements */
Das zu sortierende Array wird als ungültiger Zeiger übergeben, sodass ein Array mit einem beliebigen Elementtyp bearbeitet werden kann. Die nächsten beiden Argumente geben qsort()
wie viele Elemente im Array erwartet werden sollen und wie groß jedes Element in Byte ist.
Das letzte Argument ist ein Funktionszeiger auf eine Vergleichsfunktion, die selbst zwei leere Zeiger verwendet. Indem der Aufrufer diese Funktion zur Verfügung stellt, kann qsort()
Elemente eines beliebigen Typs effektiv sortieren.
Hier ist ein Beispiel für eine solche Vergleichsfunktion zum Vergleichen von Schwimmern. Beachten Sie, dass alle Vergleichsfunktionen, die an qsort()
werden, diese Art Signatur haben müssen. Die Art und Weise, wie sie polymorph gemacht wird, besteht darin, die leeren Zeigerargumente auf Zeiger des zu vergleichenden Elementtyps umzuwandeln.
int compare_floats(const void *a, const void *b)
{
float fa = *((float *)a);
float fb = *((float *)b);
if (fa < fb)
return -1;
if (fa > fb)
return 1;
return 0;
}
Da wir wissen, dass qsort diese Funktion zum Vergleichen von Floats verwendet, wandeln wir die Argumente des leeren Zeigers wieder in Float-Pointer um, bevor sie dereferenzieren.
Nun ist die Verwendung der polymorphen Funktion qsort auf einem Array "Array" mit der Länge "len" sehr einfach:
qsort(array, len, sizeof(array[0]), compare_floats);