Der "Fehler" hier ist einfach, dass der Zeiger nicht initialisiert ist.
In C++ zeigt ein Pointer, so er nicht initialisiert wird, einfach
"irgendwohin". Genau so wie eine Variable einfach "irgendeinen" Wert hat
wenn sie nicht initialisiert wird. Ein Pointer ist nämlich im Endeffekt
nix anderes als eine Variable in der Breite des Adressbusses (also 32
bzw. 64 bit) wo halt eine Zahl drin steht, die dann eben als Adresse
interpretiert werden soll statt als Zahl. Und da steht nun eben ohne
Initialisierung irgendeine Adresse drin. Eine Adresse, die mit nahezu
perfekter Sicherheit NICHT Dir gehört.
Im Debug-Modus werden Variablen häufig standardmäßig mit 0
initialisiert. Sowas testet man entsprechend mit maximaler Optimierung,
damit genau sowas vom Compiler eben nicht gemacht wird. Das nebenbei.
Was in Deinem Beispiel passiert ist im Endeffekt das gleiche was hier
passieren würde mit "normalen" Variablen:
1 | int c, d, e, f;
|
2 | printf("%d %d %d %d\n", c, d, e, f);
|
Jedes Mal wenn Du diesen Code ausführst, wirst Du wahrscheinlich anderes
angezeigt bekommen. Zumindest wenn Dein Rechner schon 'ne Weile in
Gebrauch war und entsprechend Datenmüll im Speicher noch rumliegt. Tut
nicht weh, weil man's eben aufräumen soll bevor man's verwendet.
Bei "normalen" Variablen existieren diese zur Beschreibung bereits wenn
Du sie deklarierst. Der Aufruf
1 | unsigned int c;
|
2 | c = 0x08154711;
|
ist demnach durchaus zulässig. Was hier passiert ist, dass der Compiler
im ram nach hinreichend Platz sucht um eine integer Variable anzulegen,
merkt sich wo der Platz war und weiß, dass er jedesmal, wenn du was von
der Variablen "c" willst er in diesen Platz schreiben soll. In "c" steht
jetzt "irgendwas" drin. Wir haben ja auch nix reingetan (noch). Wir
haben mit "unsigned int c" dem Compiler nur gesagt, er soll uns wo Platz
für 'ne Int suchen.
Mit der 2. Zeile wird nun in diesen Platz der Wert 0x08154711
geschrieben. Das funktioniert.
Nun gucken wir doch mal was bei Zeigern passiert.
1 | unsigned int *c;
|
2 | *c = 0x08154711;
|
In der 1. Zeile sagen wir dem Compiler, er soll uns wo Platz für eine
Adresse suchen. Wir sagen ihm sogar, dass diese Adresse irgendwann mal
eine unsigned int enthalten wird. Wesentlich dabei ist, dass der Platz
für den ZEIGER hier reserviert wird. NICHT der Platz für die unsigned
int auf die er zeigt. Es wird nicht, wie oft behauptet wird, "nichts"
vom Compiler hier reserviert. Es wird der Platz für den ZEIGER
reserviert. Nicht, natürlich, der Platz auf den er zeigt. Genauso wie
"unsigned int c" im 1. Beispiel Müll enthielt bevor wir eine Zahl
hineinschrieben, genauso enthält "unsigned int *c" eine "Mülladresse"
bevor wir eine sinnvolle Adresse hineinschreiben. Entsprechend geht
diese Zuweisung schief, wir wollen hier den Wert 0x08154711 an eine
Stelle schreiben, die uns nicht gehört!
DAS geht dementsprechend schief und endet mit einem Programmabsturz.
Jetzt können wir entweder eine integervariable reservieren und unser *c
drauf zeigen lassen.
1 | unsigned int c;
|
2 | unsigned int *ptr_c;
|
3 | ptr_c = &c;
|
4 | c = 0x08154711;
|
5 | *ptr_c = 0x08154711; //tut genau das gleiche wie die Zeile darüber
|
Beachte: Es muss KEIN gültiger Wert an der Adresse von c stehen damit
wir ihre Adresse dem Zeiger drauf zuweisen können. Was drin steht ist
dem Zeiger herzlich egal. Wichtig ist für ihn nur, dass diese Adresse
"gültig" ist und dem Programm "gehört", also allokiert wurde, entweder
(wie hier) vom Compiler indem es eine Variable dazu gibt oder (wie wir's
gleich machen werden) vom Programmierer indem er den Speicher dynamisch
anfordert.
Wenn die Adresse auf die der Zeiger zeigt auf diese Art definiert wird,
ist es NICHT notwendig sie "manuell" freizugeben, da diese Adresse vom
compiler allokiert worden ist.
Anders siehts's aus wenn der Speicher dynamisch allokiert wird.
1 | unsigned int *ptr_c;
|
2 |
|
3 | //C - Code
|
4 | ptr_c = (unsigned int*)malloc(sizeof(unsigned int));
|
5 |
|
6 | //Alternativ dazu: C++
|
7 | ptr_c = new int;
|
8 |
|
9 | *ptr_c = 0x08154711;
|
10 | //Jetzt damit arbeiten
|
11 |
|
12 | //C - Code
|
13 | free (ptr_c);
|
14 |
|
15 | //C++ code
|
16 | delete ptr_c;
|
17 |
|
18 | ptr_c = 0; //Muss nicht sein, sorgt aber für weniger Kopfweh.
|
Speicher den Du explizit allokierst (mit malloc oder new) MUSST Du auch
wieder durch die dazu passende "Aufräumaktion" deallokieren (mit free
bzw. delete).
Ist nicht simpel. Ich weiß. Wennst es aber einmal begriffen hast ist's
echt kein Drama mehr. Ein wenig ASM hilft dabei übrigens sehr.