Tja,
was ist so ein Zeiger. Um mal im Bild des Artikels im
FAQ zu bleiben:
Wenn ein Objekt die Seite eines Buches ist (z.B. im DIN A0 Format, also sehr gross), dann ist die Seitennummer ein Zeiger auf dieses Objekt. Dieser Zeiger erfordert selbst deutlich weniger Platz als das Objekt, zumindest in den meisten Fällen, wenn man nicht eizelne Character mit Zeigern addressieren will. Ein Zeiger ist also die Speicheraddresse eines Objekts.
Wo ist nun der Unterschied zwischen den folgenden Konstruktionen?
|
C-/C++-Quelltext
|
1
2
|
CObjekt mein_objekt;
CObjekt* zeiger_auf_mein_objekt = new CObjekt;
|
In beiden Fällen gibt es eine Seite auf der das Objekt liegt. Es gibt aber Unterschiede.
Im ersten Fall liegt das Objekt auf dem Stack und im zweiten Fall auf dem Heap. Das ist erstmal nicht so spannend, und auch nicht so wichtig, ausser man hat wirklich viele von diesen Objekten, oder sie sind schrecklich gross. Allerdings ist der Stack eine Art Kladdeblock. Seiten, die auf dem Stack liegen werden gelöscht, wenn man die Funktion verlässt, und damit sind die darauf gespeicherten Objekte verschwunden. Verlassen in dem Sinn, dass man das Ende der Funktion erreicht hat, oder sie mit einem return vorzeitig verlässt. Wenn man eine weitere Funktion aufruft, werden einfach noch ein paar Seiten oben auf den Stack gelegt und mein Objekt bleibt am Leben. Diese Seiten werden nicht herausgerissen; man vergisst nur, dass man sie mal benutzt hat, und wenn man sie später wirder braucht, werden sie recycled und überschrieben. Ein Stack ist also ein dickes Buch mit einem Faden drin, der angibt, wo man was eintragen muss. Es wird nur der Faden im Buch umgesetzt.
Ein Objekt auf dem Heap wird nicht automatisch vernichtet und seine Lebenszeit ist unabhängig von den Funktionen, in denen ich es erzeuge. Allerdings muss ich selbst darauf achten, es wieder zu vernichten, wenn ich es nicht mehr brauche. Das schlimmste, was mir hier passieren kann, ist dass ich vergesse, auf welcher Seite mein Objekt steht, weil die Seitennummer (der Zeiger) auch wieder auf dem Stack liegt.
Zeiger haben noch einen wesentlichen Vorteil: ich kann damit grosse Objekte an Funktionen übergeben, ohne sie kopieren zu müssen. Jedes Objekt hat eine Adresse (auch Zeiger selbst!) also kann ich prinzipiell auch alles als Zeiger an Funktionen übergeben. (Das ist natürlich nicht immer sinnvoll oder gewünscht, die Schlagworte hier sind Call-by-Value und Call-by-Reference)
Aber mit Zeigern auf Objekte, die auf dem Stack liegen, muss man ein wenig aufpassen, weil solche Zeiger ungültig werden können, ohne dass man das sofort merkt.
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
void bar (int* zeiger_irgendwohin)
{
std::cout << *zeiger_irgendwohin << std::endl;
}
int* foo ()
{
int auf_dem_stack = 5;
bar (&auf_dem_stack); // Zeiger ist gültig
return &auf_dem_stack; // Zeiger wird ungültig
}
void foobar ()
{
int anderer_wert = 7;
}
int main ()
{
int* zeiger_aus_funktion = foo ();
bar (zeiger_aus_funktion);
foobar ();
bar (zeiger_aus_funktion);
return 0;
}
|
Du kannst ja mal überlegen, was hier ausgegeben wird (ohne das zu compilieren
) Zum Glück kann der Compiler in solchen Fällen warnen, dass man die Adresse auf eine lokale Variable als Ergebnis liefern will.
Gruss,
Rainer
PS: hmm, hier fehlen noch ein paar Beispiele und Links aber ich muss jetzt wirklich zur Arbeit