„<(°_°)> Nicht verstehen Pointers führt zu Wut, Wut führt zu Hass. Hass führt zu unsäglichem Leid.“ (frei nach Yoda)
Nein, nein. Das wird hier keine Fortsetzung von Star Wars Aber das eine oder andere Sternchen wird in den Beispielen auftauchen. Und manch einer steht auf Kriegsfuß mit ihnen, was der Grund für diesen Beitrag ist. Bringen wir also etwas Licht auf die dunkle Seite der Macht
Der Name Pointer (zu Deutsch: Zeiger) sagt schon einiges. Ein Pointer zeigt auf ein Objekt, er verweist auf ein Objekt, er kennt den Aufenthaltsort eines Objekts. Aber er ist nicht das Objekt selbst. Wie können wir uns das vorstellen? Nehmen wir an Jonas ist das "Objekt" von dem hier die Rede ist. Lena soll Jonas ein Päckchen senden. Jonas hatte Lena einen Zettel mit seiner Adresse gegeben. So weiß Lena wohin sie das Päckchen senden muss, damit es bei Jonas ankommt. Der Zettel mit der Adresse ist das, was wir im übertragenen Sinne einen Pointer nennen können.
Und tatsächlich funktioniert ein Pointer genau so. Die Pointervariable ist der Zettel und deren Wert ist die Adresse. Eine Speicheradresse in diesem Fall. Speicheradressen sind Abfolgen von Bits, die durch einen ganzzahlige Werte repräsentiert werden können, damit sie für uns Menschen etwas leichter zu begreifen sind. Aber das ist vermutlich trotzdem noch zu abstrakt. Also versuchen wir das ganze mal etwas bildlich darzustellen.
Die grünen Rechtecke sind die Bytes, die die Zahl 8 repräsentieren. (Davor und dahinter existiert natürlich auch Speicher mit irgendeinem Inhalt, aber der geht uns nichts an.) Wieso habe ich 4 Bytes grün eingefärbt? Wir haben Variable
Jedes dieser Bytes hat eine Adresse im Speicher.
Die blaue Adresse 0020A50521331000 markiert den Anfang des ersten Bytes von
Deklarieren wir nun eine Pointervariable.
Bei der Deklaration wird durch das Sternchen (je nach Kontext Value-At-, Indirection- oder Dereference-Operator genannt)
Im Speicher sieht das nun so aus:
Wie zu sehen ist, enthält der Speicherbereich von
Auch Variable
Fassen wir vorläufig zusammen: Der Wert einer Pointervariablen ist die Adresse eines Speicherbereichs. Nicht mehr und nicht weniger.
Wenn das so ist, warum reicht es dann nicht aus
Was sagt uns (oder besser dem Compiler) die Deklaration mit dem vorangestellten Typ
Ich empfehle, Deklarationen von rechts nach links zu lesen. In unserem Fall wäre das "ptr ist ein Pointer auf ein int". Wie zu Anfang schon einmal erwähnt ist
Somit gibt uns ein Pointer also zwei Informationen:
1. Die Adresse eines Speicherbereichs als Wert der Pointervariablen.
2. Die Breite des Speicherbereichs, auf den bei der Dereferenzierung des Pointers zugegriffen werden soll, als Typinformation an den Compiler.
Apropos Dereferenzierung. Der Zettel mit der Adresse von Jonas ist, für sich allein genommen, noch nicht besonders wertvoll. Er wird erst dann interessant, wenn die Adresse wirklich benötigt wird, nämlich um ein Päckchen an die Adresse zu versenden. So verhält es sich auch mit den Pointervariablen. Wir benötigen einen Pointer um Werte aus dem Speicherbereich zu lesen, oder um Werte in den Speicherbereich zu schreiben, auf den er zeigt. Bleiben wir bei unserem Beispiel.
Die Ausgabe dieses Codeschnipsels ist
Wir erinnern uns,
Nun greifen wir erneut auf den Speicherbereich zu. Diesmal ändern wir den Wert auf 10. Der zweite
Achtung Stolpergefahr: Bei der Deklaration einer Pointervariablen zeigt das vorangestellte Sternchen an, dass die Variable ein Pointer ist. Wenn die Variable deklariert ist und eine Speicheradresse als Wert zugewiesen bekommen hat, wird das Sternchen dazu genutzt um durch Dereferenzierung auf den Speicherbereich zuzugreifen, auf den der Pointer zeigt. Es hat also unterschiedliche Bedeutungen, je nach Kontext. Der Name der Variablen ist natürlich auch immer noch
Arrays und Pointer verbindet eine verwandtschaftliche Beziehung. Sie sind aber nicht dasselbe. Ein Array ist immer auch ein Pointer, aber ein Pointer ist noch längst kein Array.
Sehen wir uns das noch einmal an einem Beispiel an.
Wie kann man sich die Visualisierung des Speichers dafür vorstellen?
Die beiden Werte 8 und 4 stehen in einem zusammenhängenden Speicherbereich. Jede dieser Werte/Arrayelemente nimmt die Breite eines
Es ist kein Zufall, dass ich den Variablenname
Die Ausgabe könnte so aussehen:
Die Variable
Ein Array kann einer Pointervariablen zugewiesen werden oder an einen Pointerparameter übergeben werden. In diesem Fall geht die Längeninformation verloren, da ein Pointer diese Information nicht halten kann. Man spricht von Array-To-Pointer Decay (Zeigerverfall).
Wenn ein Array ein Pointer ist, warum ist ein Pointer dann kein Array?
Wir haben einen Pointer deklariert und wir haben ihm die Speicheradresse 4711 zugewiesen. Aber liegt diese Speicheradresse im Adressbereich, den unser Prozess zugewiesen bekommen hat? Nein. Wir dürfen nicht auf irgendeinen Speicherbereich zugreifen, von dem das Betriebssystem nicht weiß dass wir ihn verwenden. Ein ...
... würde also eine Speicherverletzung verursachen und sehr wahrscheinlich zum Absturz des Programms führen. Ein Pointer ist nur dann valide, wenn er auf einen gültigen Speicherbereich zeigt. Aber auch der muss noch kein Array sein, wie ganz zu Anfang gezeigt.
Eine weitere Möglichkeiten Speicher anzufordern und einen validen Pointerwert zu bekommen, ist die Allokation mittels den Funktionen
Wie im Beispiel gezeigt, lässt sich auf Elemente eines zusammenhängenden Speicherbereichs mit dem
Nun, dahinter steckt Pointerarithmetik. Darunter versteht man die Addition/Subtraktion eines ganzzahligen Wertes zu/von einem Pointerwert.
Mit
Ausgabe:
Wie könnte man den Index 0 in
Ausgabe:
Wenn das also funktioniert, ließe sich auf die 4 zugreifen, wenn wir 1 zu
Ausgabe:
Mit
entspricht
Beispiel:
Ausgabe:
Wir haben bereits mit ein paar Operatoren gearbeitet, die spezielle Bedeutungen für Pointer haben. Ist aber noch nicht vollständig erklärt.
Jetzt gibt es noch zwei Kleinigkeiten zu erklären, nämlich einen speziellen Pointerwert und einen speziellen Pointertyp:
Starten wir mit dem Wert NULL. Das ist relativ einfach, denn
Dann gibt es noch den speziellen void* Pointertyp. Wie bei jedem anderen Pointer, ist auch hier der Wert eine Speicheradresse. Allerdings ist
Hier ist unbekannt, wie groß der Speicherbereich ist, auf den
Welchen Sinn hat er dann aber? Ein
Mit meiner Beschreibung bin ich am Ende, aber bezüglich C++ noch ein kurzes Addendum:
Ich habe als Tags C und C++ angegeben, vor die Überschrift aber nur C gesetzt. Grundsätzlich ist alles was ich hier geschrieben habe ebenso gut auf C++ übertragbar. Allerdings sollte jeder der in C++ Pointer im Code hat, dies ganz pragmatisch als suspekt annehmen und ernsthaft darüber nachdenken, ob er dort nicht bereits einen Designfehler eingebaut hat. Wenn eine API oder Library Pointer verlangt, ist man relativ machtlos. Aber auch nicht ganz, denn manche Containerklassen haben Methoden, die einen Pointer auf die Rohdaten zurückgeben, der dann weitergegeben werden kann.
In Parameterlisten eigener Funktionen braucht man für C++ in der Regel nie Pointer. Stattdessen sollte man mit Referenzen arbeiten. So erspart man sich mindestens die Prüfung ob ein
Ich hoffe, das „<(-_-)> Ins Exil gehen ich werde müssen. Versagt ich habe.” kannst du von nun an Yoda überlassen. Diese kurze Einführung sollte genügen, um im Umgang mit Pointern nicht mehr völlig zu versagen Auch wenn sie längst keinen Anspruch auf Vollständigkeit erhebt.
Anregungen zu Ergänzungen oder Korrekturen nehme ich natürlich gern an.
Nein, nein. Das wird hier keine Fortsetzung von Star Wars Aber das eine oder andere Sternchen wird in den Beispielen auftauchen. Und manch einer steht auf Kriegsfuß mit ihnen, was der Grund für diesen Beitrag ist. Bringen wir also etwas Licht auf die dunkle Seite der Macht
Der Name Pointer (zu Deutsch: Zeiger) sagt schon einiges. Ein Pointer zeigt auf ein Objekt, er verweist auf ein Objekt, er kennt den Aufenthaltsort eines Objekts. Aber er ist nicht das Objekt selbst. Wie können wir uns das vorstellen? Nehmen wir an Jonas ist das "Objekt" von dem hier die Rede ist. Lena soll Jonas ein Päckchen senden. Jonas hatte Lena einen Zettel mit seiner Adresse gegeben. So weiß Lena wohin sie das Päckchen senden muss, damit es bei Jonas ankommt. Der Zettel mit der Adresse ist das, was wir im übertragenen Sinne einen Pointer nennen können.
Und tatsächlich funktioniert ein Pointer genau so. Die Pointervariable ist der Zettel und deren Wert ist die Adresse. Eine Speicheradresse in diesem Fall. Speicheradressen sind Abfolgen von Bits, die durch einen ganzzahlige Werte repräsentiert werden können, damit sie für uns Menschen etwas leichter zu begreifen sind. Aber das ist vermutlich trotzdem noch zu abstrakt. Also versuchen wir das ganze mal etwas bildlich darzustellen.
int num = 8;
num
ist eine Variable vom Typ int
, die den Wert 8 hat. Wie sieht das im Speicher aus?Die grünen Rechtecke sind die Bytes, die die Zahl 8 repräsentieren. (Davor und dahinter existiert natürlich auch Speicher mit irgendeinem Inhalt, aber der geht uns nichts an.) Wieso habe ich 4 Bytes grün eingefärbt? Wir haben Variable
num
als int
deklariert. Jeder Typ hat eine bestimmte Typbreite, die der Compiler kennt. Ich bin hier von 4 Bytes ausgegangen, da das die Breite ist, die ein int
in den meisten modernen C und C++ Implementierungen hat. Der sizeof
Operator würde Aufschluss darüber bieten. Er gibt die Breite in Bytes wieder.Jedes dieser Bytes hat eine Adresse im Speicher.
Die blaue Adresse 0020A50521331000 markiert den Anfang des ersten Bytes von
num
. Die graue Adresse markiert bereits Speicher, der nicht mehr zu num
gehört.Deklarieren wir nun eine Pointervariable.
int *ptr = #
Bei der Deklaration wird durch das Sternchen (je nach Kontext Value-At-, Indirection- oder Dereference-Operator genannt)
*
markiert, dass es sich um einen Pointer handelt. Der Address-Of-Operator &
gibt die Adresse von num
wieder. Sie wird der Variablen ptr
zugewiesen.Im Speicher sieht das nun so aus:
Wie zu sehen ist, enthält der Speicherbereich von
ptr
(blau markierte Bytes) nun die Speicheradresse 0020A50521331000 der Variablen num
(in der Regel in umgekehrter Bytereihenfolge, aber das ist ein anderes Thema).Auch Variable
ptr
hat wieder eine Speicheradresse (orange), die mittels &ptr
einer Variablen vom Typ int**
zugewiesen werden könnte. Das aber nur nebenher. Wir bleiben hier bei der einfachen Referenzierung.Fassen wir vorläufig zusammen: Der Wert einer Pointervariablen ist die Adresse eines Speicherbereichs. Nicht mehr und nicht weniger.
Wenn das so ist, warum reicht es dann nicht aus
ptr
wie folgt zu deklarieren?*ptr = #
Was sagt uns (oder besser dem Compiler) die Deklaration mit dem vorangestellten Typ
int *ptr
Ich empfehle, Deklarationen von rechts nach links zu lesen. In unserem Fall wäre das "ptr ist ein Pointer auf ein int". Wie zu Anfang schon einmal erwähnt ist
int
das von der Programmiersprache vorgegebene Schlüsselwort, dem Compiler zu sagen, dass es sich um einen Speicherbereich einer bestimmten Breite handelt (4 Bytes im Beispiel).Somit gibt uns ein Pointer also zwei Informationen:
1. Die Adresse eines Speicherbereichs als Wert der Pointervariablen.
2. Die Breite des Speicherbereichs, auf den bei der Dereferenzierung des Pointers zugegriffen werden soll, als Typinformation an den Compiler.
Apropos Dereferenzierung. Der Zettel mit der Adresse von Jonas ist, für sich allein genommen, noch nicht besonders wertvoll. Er wird erst dann interessant, wenn die Adresse wirklich benötigt wird, nämlich um ein Päckchen an die Adresse zu versenden. So verhält es sich auch mit den Pointervariablen. Wir benötigen einen Pointer um Werte aus dem Speicherbereich zu lesen, oder um Werte in den Speicherbereich zu schreiben, auf den er zeigt. Bleiben wir bei unserem Beispiel.
C:
int num = 8;
int *ptr = #
printf("%d\n", *ptr);
*ptr = 10;
printf("%d\n", *ptr);
printf("%d\n", num);
Wir erinnern uns,
ptr
hält die Adresse von num
. Im Speicherbereich von num
steht der Wert 8. Wenn wir nun die Variable ptr
mit dem vorangestellten *
dereferenzieren, greifen wir auf den Speicherbereich zu, auf den der Pointer zeigt. Darum bekommen wir 8 als Ausgabe des ersten printf
Aufrufs.Nun greifen wir erneut auf den Speicherbereich zu. Diesmal ändern wir den Wert auf 10. Der zweite
printf
Aufruf gibt auch wie erwartet 10 für den dereferenzierten Pointer aus. Der dritte printf
Aufruf verdeutlicht noch einmal, dass wir tatsächlich in den Speicherbereich von num
geschrieben haben.Achtung Stolpergefahr: Bei der Deklaration einer Pointervariablen zeigt das vorangestellte Sternchen an, dass die Variable ein Pointer ist. Wenn die Variable deklariert ist und eine Speicheradresse als Wert zugewiesen bekommen hat, wird das Sternchen dazu genutzt um durch Dereferenzierung auf den Speicherbereich zuzugreifen, auf den der Pointer zeigt. Es hat also unterschiedliche Bedeutungen, je nach Kontext. Der Name der Variablen ist natürlich auch immer noch
ptr
und nicht etwa *ptr
.Arrays und Pointer verbindet eine verwandtschaftliche Beziehung. Sie sind aber nicht dasselbe. Ein Array ist immer auch ein Pointer, aber ein Pointer ist noch längst kein Array.
Sehen wir uns das noch einmal an einem Beispiel an.
int arr[2] = {8, 4};
Wie kann man sich die Visualisierung des Speichers dafür vorstellen?
Die beiden Werte 8 und 4 stehen in einem zusammenhängenden Speicherbereich. Jede dieser Werte/Arrayelemente nimmt die Breite eines
int
ein (je 4 Bytes im Beispiel).Es ist kein Zufall, dass ich den Variablenname
arr
in die Zeile mit dem Pointerwert geschrieben und die [2]
zu den Werten gezogen habe, wie hier zu sehen sein wird:
C:
int arr[2] = {8, 4};
printf("Wert von arr[0]: %d\n", arr[0]);
printf("Wert von arr[1]: %d\n", arr[1]);
printf("Wert von arr: %p\n", arr);
printf("Adresse von arr[0]: %p\n", &arr[0]);
printf("Adresse von arr[1]: %p\n", &arr[1]);
Die Variable
arr
hält also die Adresse des Beginns des Speicherbereichs des Arrays. Nicht mehr. Das int
ist das Schlüsselwort für den Typ und gibt dem Compiler Information über die Größe eines Elements. Die [2]
sagt dem Compiler für wie viele Elemente Speicher benötigt wird. Aber nur wo das Array sichtbar ist, also wo es deklariert wurde. Nur dort ist bekannt, wie groß das Array ist. Den Typ des Arrays könnte man auch als int[2]
verstehen (bei einigen Sprachen, wie C# oder Java, wäre das die Schreibweise, und somit syntaktisch etwas logischer als in C oder C++). Ein sizeof
ergäbe somit die Elementgröße, multipliziert mit der Anzahl der Elemente.Ein Array kann einer Pointervariablen zugewiesen werden oder an einen Pointerparameter übergeben werden. In diesem Fall geht die Längeninformation verloren, da ein Pointer diese Information nicht halten kann. Man spricht von Array-To-Pointer Decay (Zeigerverfall).
Wenn ein Array ein Pointer ist, warum ist ein Pointer dann kein Array?
int *ptr = (int*)4711;
Wir haben einen Pointer deklariert und wir haben ihm die Speicheradresse 4711 zugewiesen. Aber liegt diese Speicheradresse im Adressbereich, den unser Prozess zugewiesen bekommen hat? Nein. Wir dürfen nicht auf irgendeinen Speicherbereich zugreifen, von dem das Betriebssystem nicht weiß dass wir ihn verwenden. Ein ...
*ptr = 8;
... würde also eine Speicherverletzung verursachen und sehr wahrscheinlich zum Absturz des Programms führen. Ein Pointer ist nur dann valide, wenn er auf einen gültigen Speicherbereich zeigt. Aber auch der muss noch kein Array sein, wie ganz zu Anfang gezeigt.
Eine weitere Möglichkeiten Speicher anzufordern und einen validen Pointerwert zu bekommen, ist die Allokation mittels den Funktionen
malloc
, calloc
, realloc
in C, oder den Operatoren new
und new[]
in C++. Darauf gehe ich hier aber nicht weiter ein. Entsprechende Referenzen finden sich im Internet.Wie im Beispiel gezeigt, lässt sich auf Elemente eines zusammenhängenden Speicherbereichs mit dem
[]
-Operator zugreifen. Wir wissen aber auch dass ein Pointer mittels *
dereferenziert werden kann um auf den Speicherbereich auf den ein Pointer zeigt zuzugreifen. Und wir wissen dass ein Array auch ein Pointer ist. Wie geht das alles zusammen?Nun, dahinter steckt Pointerarithmetik. Darunter versteht man die Addition/Subtraktion eines ganzzahligen Wertes zu/von einem Pointerwert.
Mit
arr[0]
greifen wir auf das erste Element zu. arr
ist ein Pointer auf das erste Element, wie die Ausgabe der Adressen gezeigt hat. Was würde passieren wenn wir arr
dereferenzieren?printf("Wert in *arr: %d\n", *arr);
Ausgabe:
Wie könnte man den Index 0 in
arr[0]
in oben gezeigte Dereferenzierung einbeziehen, um immer noch auf den Wert 8 zuzugreifen? Nun, wenn man 0 zu einer Speicheradresse addiert, haben wir immer noch dieselbe Speicheradresse. Test:printf("Wert in *(arr + 0): %d\n", *(arr + 0));
Ausgabe:
Wenn das also funktioniert, ließe sich auf die 4 zugreifen, wenn wir 1 zu
arr
addieren und dereferenzieren?printf("Wert in *(arr + 1): %d\n", *(arr + 1));
Ausgabe:
Mit
arr + n
verschieben wir den Pointer also um die n
-fache Breite des Elementtyps. Der []
-Operator erfüllt somit 2 Aufgaben. Er inkrementiert den Pointer um den angegebenen Index und dereferenziert den resultierenden Pointer. Es giltarr[n]
entspricht
*(arr + n)
n
könnte dabei durchaus auch negativ sein, solange der resultierende Pointer auf validen Speicher zeigt.Beispiel:
C:
int arr[2] = {8, 4};
int *ptr = arr + 1;
printf("Wert von ptr[-1]: %d\n", ptr[-1]);
Wir haben bereits mit ein paar Operatoren gearbeitet, die spezielle Bedeutungen für Pointer haben. Ist aber noch nicht vollständig erklärt.
- Mit dem Indirection-Operator
*
deklarieren wir eine Pointervariable oder greifen auf den Wert zu auf die sie zeigt. - Mit dem Address-Of-Operator
&
bekommen wir die Adresse von einem Wert, die wir einem Pointer zuweisen können. - Mit
+
,-
,++
,--
,+=
oder-=
können Pointerwerte verändert werden (Pointerarithmetik). Die Schrittweite ist dabei automatisch die Breite des Typs. - Mit dem Subscript-Operator
[]
wird der Pointerwert verändert und gleichzeitig auf den Wert zugegriffen auf den der Pointer zeigt.ptr[n]
ist die Kurzform für*(ptr + n)
- Der
->
Operator ist eine Spezialform des Member-Access-Operators für Pointervariablen, die auf Strukturen zeigen. Auch er dereferenziert den Pointer und kürzt die Schreibweise(*ptr).member
durchptr->member
ab. - Vergleichsoperatoren können mit Pointerwerten verwendet werden, sofern die Operanden Pointerwerte desselben Objekts sind (bspw. Pointer auf Elemente eines Arrays). Abweichend von dieser Regel, kann auf Gleichheit oder Ungleichheit mit
NULL
geprüft werden. Bei diesen Vergleichen ist zu bedenken, dass lediglich Speicheradressen verglichen werden und nicht die Werte auf die diese Pointer zeigen.
Jetzt gibt es noch zwei Kleinigkeiten zu erklären, nämlich einen speziellen Pointerwert und einen speziellen Pointertyp:
Starten wir mit dem Wert NULL. Das ist relativ einfach, denn
NULL
ist eine Speicheradresse mit einem festgelegten Wert (nicht zwingend 0, aber in den meisten Implementierungen schon). Wie jede andere Adresse die nicht vom Betriebssystem für unseren Prozess reserviert wurde, zeigt auch NULL
auf einen ungültigen Speicherbereich. Wozu ist das dann gut? NULL
indiziert einen definierten Fehlerzustand. Auch wenn es so eigentlich nicht richtig ist, kann man diesen Zustand recht gut mit "NULL zeigt auf nichts" erklären, da er in diesem Sinne verwendet wird. Viele Standardfunktionen nutzen diesen Wert. Beispielsweise geben Funktionen, die einen Pointer zurückgeben, oft dann NULL
zurück, wenn die Funktion fehlgeschlagen ist. Sollte ein Pointer in der Parameterliste stehen, so erwarten manche Funktionen NULL
als Argument, um anzuzeigen, dass dieser Parameter nicht verwendet werden soll. Man sollte aber nicht blindlings davon ausgehen, dass NULL
korrekt behandelt wird, sondern die Funktionsreferenzen sorgsam lesen, um zu wissen was in diesem Fall passiert.Dann gibt es noch den speziellen void* Pointertyp. Wie bei jedem anderen Pointer, ist auch hier der Wert eine Speicheradresse. Allerdings ist
void
ein unvollständiger Typ. Das heißt, seine Breite ist nicht bekannt. Beispiel:void *ptr = (void*)#
Hier ist unbekannt, wie groß der Speicherbereich ist, auf den
ptr
zeigt. Somit darf ein Pointer auf void
weder dereferenziert werden, noch darf Pointerarithmetik angewendet werden.Welchen Sinn hat er dann aber? Ein
void*
ist dazu da Speicheradressen auf Speicherbereiche unterschiedlichster Typen zu transportieren. (Er erfüllt den Zweck eines sogenannten untypisierten Pointers.) Dein eigener Programmcode, oder Implementierungen in Libraries, sind dafür verantwortlich den void*
in den Pointertyp zu casten, der letztlich benötigt wird. Dass man dabei Fehler machen kann und dass das einige Gefahren in sich birgt, sollte selbsterklärend sein.Mit meiner Beschreibung bin ich am Ende, aber bezüglich C++ noch ein kurzes Addendum:
Ich habe als Tags C und C++ angegeben, vor die Überschrift aber nur C gesetzt. Grundsätzlich ist alles was ich hier geschrieben habe ebenso gut auf C++ übertragbar. Allerdings sollte jeder der in C++ Pointer im Code hat, dies ganz pragmatisch als suspekt annehmen und ernsthaft darüber nachdenken, ob er dort nicht bereits einen Designfehler eingebaut hat. Wenn eine API oder Library Pointer verlangt, ist man relativ machtlos. Aber auch nicht ganz, denn manche Containerklassen haben Methoden, die einen Pointer auf die Rohdaten zurückgeben, der dann weitergegeben werden kann.
std::string
oder std::vector
wären hier beispielhaft zu nennen. Es ist kaum nötig, selber mit new
zu hantieren. Wenn man wirklich nicht um handgerollte Allokationen im Code herum kommt, dann aber wenigstens Smart Pointer verwenden (wie bspw. die std::unique_ptr
Klasse), bei denen sichergestellt ist, dass reservierter Speicher über den Destruktor in jedem Fall auch wieder freigegeben wird.In Parameterlisten eigener Funktionen braucht man für C++ in der Regel nie Pointer. Stattdessen sollte man mit Referenzen arbeiten. So erspart man sich mindestens die Prüfung ob ein
NULL
Pointer bzw. nullptr
übergeben wurde.Ich hoffe, das „<(-_-)> Ins Exil gehen ich werde müssen. Versagt ich habe.” kannst du von nun an Yoda überlassen. Diese kurze Einführung sollte genügen, um im Umgang mit Pointern nicht mehr völlig zu versagen Auch wenn sie längst keinen Anspruch auf Vollständigkeit erhebt.
Anregungen zu Ergänzungen oder Korrekturen nehme ich natürlich gern an.