Einsatz von new
-
Servus,
ich hab mit new immer wieder so meine Probleme und würde mich auf ein paar Antworten eurerseits.
Folgendes Beispiel funktioniert ohne Probleme im VC (ohne Optimierungen) und das wirft ein paar Fragen auf.
Meines Kennissestandes:
- new reserviert im Heap einen Speicherplatz
- der Heap lebt solange, wie der aktuelle Prozess lebt
- mittels "Test t" wird der Speicherplatz im Stack angelegt und beim Verlassen der Funktion wieder freigegeben
Wieso sind dann die Zahlen 5 und 2 noch vorhanden? Nach dem Verlassen des Konstruktors sollte der Speicher wieder freigegeben werden, oder?
Gibt es eine Ausnahme für sogenannte Primitive-Daten (z.B. int, long, float, double, ....)?Die Fragen sind etwas trivial, aber ich kann aus den Bücher & Co. nicht entnehmen, wann ich genau new benutzen muss und wann nicht. Ich bitte nur denen auf diese Frage zu antworten, die es ernst meinen. Auf Weisheiten ala Dro...el kann ich verzichten

#import <iostream> using std::cout; class Abc { private: int zahl; public: Abc(); ~Abc(); int getZahl(); }; Abc::Abc() { this->zahl = 5; } Abc::~Abc() {} int Abc::getZahl() { return this->zahl; } class Test { private: Abc ab; int cnt; public: Test(); ~Test(); Abc* getAbc(); int getCnt(); }; Test::Test() { //this->ab = new Abc(); this->cnt = 2; } Test::~Test() { //delete this->ab; } Abc* Test::getAbc() { return &this->ab; } int Test::getCnt() { return this->cnt; } int main() { Test t; cout << "Test.cnt: "; cout << t.getCnt(); cout << "\n"; cout << "Abc.zahl: "; cout << t.getZahl()->getZahl(); } // Ausgabe erfolgreich. Ohne jegliche Fehler
-
Siassei schrieb:
Wieso sind dann die Zahlen 5 und 2 noch vorhanden? Nach dem Verlassen des Konstruktors sollte der Speicher wieder freigegeben werden, oder?
Nein, da das Objekt t noch existiert, und du Membervariablen einsetzt. Letztere werden auch mit dem Objekt freigegeben.
Gehen wir mal einige Fälle durch:
a) Lokale, nichtstatische Variablen
Lokale Variablen werden lokal deklariert und gelten von diesem Punkt aus, bis zum verlassen des jeweiligen "Scopes"int foo() { // <-- Hier beginnt ein Scope int a = 4; { // <-- Hier beginnt ein Scope int b = 2; } // <-- Hier endet der innere Scope (b wird ungültig) return a; // Durch return wird auch das Scope verlassen } // (a wird ungültig, eine Kopie zurückgegeben [*])* Tatsächlich kann es sein, das ein Compiler einige Kopien wegoptimieren kann.
b) Nicht-Statische Membervariablen
Membervariablen gelten für die Lebensdauer des Objektesclass Bar { private: int a; // Deklaration innerhalb der Klasse, nicht in einer Funktion! public: Bar() { a = 4; // Hier wird a geändert, a ist Objektweit gültig int b = 2; } // b wird hier ungültig, da lokale Variable, a existiert aber weiter ~Bar() { } // Hier wird a tatsächlich ungültig int GetA() const { return a; // Hier wird a zurückgegeben } }; int main() { Bar bar; // Hier wird ein Objekt angelegt, der Konstruktor aufgerufen bar.GetA(); // Gültig, da Objekt noch existiert } // Hier wird bar ungültig, damit auch bar.ac) Undefiniertes Verhalten (Anmerkung)
Nun gehen wir einen Schritt weiter: Ungültig werden, heißt nicht das die Werte sofert aus dem Speicher entfernt werden - Nur können sie jederzeit überschrieben werden, der Zugriff ist also undefiniert.#include <iostream> int main() { int *pa = 0; { int a = 4; pa = &a; // pa zeigt nun auf den Speicherbereich von a } // a wird ungültig... std::cout << *pa; // ... was nun im Speicher an der Stelle steht ist // undefiniert, hier ist die Wahrscheinlichkeit aber noch recht hoch // das tatsächlich noch 4 ausgegeben wird. Aber wie gesagt ungültig. }d) statische lokale Variablen/statische Membervariablen
Statische Variablen sind eine Besonderheit. Statische lokale Variablen werden beim ersten Aufruf angelegt, bleiben aber bis zum Programmende gültig, und behalten ihren Wertvoid foo() { static int a = 1; // Beim ersten Aufruf wird a angelegt, und mit 1 initialisiert. a += 2; // Pro durchgang wird a um 2 erhöht std::cout << a; // Beim ersten Durchgang 3, beim zweiten 5... }Statische Membervariablen werden spätestens mit der ersten Instanz der Klasse (oder dem ersten Zugriff, was eher erfolgt) angelegt. Und gelten im Gegensatz zu nicht statischen Membervariablen Klassenweit (statt Objektweit). Am besten ist dies Am Beispiel zu erklären:
// Header #ifndef BAR_HEADER #define BAR_HEADER class Bar { static int a; int b; // Hier zeige ich noch eine Besonderheit, erkläre ich weiter unten Bar(int b); }; #endif// Source #include "Bar.h" int Bar::a = 0; // Statischen Member Bar::a einmalig initialisieren (Merkwürdige // schreibweise, ich weiß // Das int b in der Parameterliste ist ein anderes als die Membervariable, // es "verdeckt" es... Bar::Bar(int b) // Der Parameter gilt Methodenweit (wie eine lokale Variable) { b += 2; // Hiermit erhöhe ich den Parameter, nicht die Membervariable this->b = b; // Über this-> greife ich auf die Membervariable zu ++a; // Hiermit erhöhe ich die Statische Membervariable um 1. }// main.cpp #include <iostream> #include "Bar.h" int main() { std::cout << Bar::a << std::endl; // Statische Membervariablen kann ich // auch ohne Objekt ansprechen // Ausgabe: 0 Bar bar1(4); std::cout << bar1.a << ", " << bar1.b << ", " << Bar::a << std::endl; // Ausgabe: 1, 6, 1 Bar bar2(10); std::cout << bar2.a << ", " << bar2.b << ", " << Bar::a << std::endl; // Ausgabe: 2, 12, 2 // Ausgabe: 2, 2, 2 (Den es handelt sich um EINE Variable [Klassenweit]) std::cout << bar1.a << ", " << bar2.a << ", " << Bar::a; // Ausgabe: 6, 12 (Den es handelt sich um unterschiedliche Variablen [Da Objektweit] std::cout << bar1.b << ", " << bar2.b; }e) new/delete... Zeiger und auf Heap allozierten Speicher
Erzeugen wir erst einmal ein Speicherleak
int main() { { // Dynamisches allozieren von Speicher, ein Int wird auf den Heap // abgelegt, die Adresse wird pa zugewiesen. int *pa = new int(4); } // pa wird ungültig... // ... aber der Speicher bleibt alloziert! ==> Speicherleak }Es wurde hier eine lokale Variable angelegt pa, die auf einen Speicherbereich mit einem int-Wert verweist.
( Stack ) ( Heap ) [Adresse] --> [int: 4] int* paDie lokale Variable wird aber am Ende des Scopes ungültig, der Speicherbereich wurde aber nicht freigegeben.
( Heap ) [int: 4]Zu jedem new gehört ein delete, zu jedem new[] ein delete[]
int main() { // [1.] int *pa = 0; // pa wird angelegt und die Adresse mit 0 initialisiert [2.] pa = new int(4); // Speicher auf dem Heap wird alloziert [3.] delete pa; // Der Speicher auf dem Heap wird freigegeben, [4.] // pa existiert aber weiterhin } // <-- Hier wird pa ungültig [5.]Noch mal im Einzelschritt (Nehmen wir mal als Adresse des allozierten Speichers 0xA6F0 an...):
___________________ ________________ | Stack | | Heap | 1. - - 2. int * pa = 0x0000 --x - 3. pa = 0xA6F0 ----> (int) 4 4. pa = 0xA6F0 ----> undefiniert [undefiniert: da freigegeben] 5. - -cu André
-
Servus André,
tausend Dank für deinen Beitrag, der hat die letzten Unsicherheiten ausgelöscht

Noch kurz eine Bestätigung. Folgendes ist somit undefiniert
// main.cpp #include <iostream> #include "Bar.h" int main() { std::cout << Bar::a << std::endl; // Statische Membervariablen kann ich // auch ohne Objekt ansprechen // Ausgabe: 0 Bar bar1(4); std::cout << bar1.a << ", " << bar1.b << ", " << Bar::a << std::endl; // Ausgabe: 1, 6, 1 Bar bar2(10); std::cout << bar2.a << ", " << bar2.b << ", " << Bar::a << std::endl; // Ausgabe: 2, 12, 2 // Ausgabe: 2, 2, 2 (Den es handelt sich um EINE Variable [Klassenweit]) std::cout << bar1.a << ", " << bar2.a << ", " << Bar::a; // Ausgabe: 6, 12 (Den es handelt sich um unterschiedliche Variablen [Da Objektweit] std::cout << bar1.b << ", " << bar2.b; Bar bar3(6); { Bar bar4(8); bar3.setA(&bar4.a); // Klasse Bar erweitert: void setA(int* a) { this->a = *a; } }// bar4 wird ungültig std::cout << bar4.a // undefiniert, wahrscheinlich 8 }
-
Siassei schrieb:
Bar bar3(6); { Bar bar4(8); bar3.setA(&bar4.a); // Klasse Bar erweitert: void setA(int* a) { this->a = *a; } }// bar4 wird ungültig std::cout << bar4.a // undefiniert, wahrscheinlich 8 }Kompiliert nicht, da bar4 mit der schließenden Klammer out of scope ging und es im umschließenden scope keinen Bezeichner "bar4" gibt.
Wenn du eigentlich sagen wollteststd::cout << bar3.a << std::endl;dann stimmt die Annahme, daß es undefiniert nicht, weil du in Bar::setA(int *a) den Pointer dereferenziert hast (*a), womit du den Wert nach this->a kopierst.
Undefiniertes Verhalten wäre eher sowas:
class Bar { //... int *a; //... void printA() { std::cout << *(this->a) << std::endl; } void setA(int *a) { this->a = a; } //... }; //... Bar bar3(6); { Bar bar4(8); bar3.setA(&bar4.a); } bar3.printA(); // Ausgabe ist undefiniert, gibt aber wahrscheinlich 8 aus.
-
Vielleicht solltest du noch den Unterschied zwischen Wert, Zeiger und Referenz kennen lernen. Betrachten wir erstmal die ersten Beiden.
Grundsätzlich stellt die Deklaration einer Variablen irgendwo im Speicher Platz bereit (Bezüglich Referenzen kann man dies nicht ganz eindeutig sagen, daher später mehr dazu).
Das heißt, das im folgenden Code...
int a = 4; int b = 5; int *pa = &a; int *pb = &b;4 Plätze im Speicher bereit. Die ersten Beiden sind groß genug um jeweils einen Int-Wert, die Letzten beiden um eine Adresse eines Int-Wertes aufnehmen zu können.
Nehmen wir mal an, das int 4 Byte belegt, und die Werte hintereinander im Speicher liegen, zudem die Adressen bei 0x10203040 beginnen:
Im Speicher sähe es dann etwa wie folgt aus (was erst einmal verwirrt)
... 00 00 00 04 00 00 00 05 10 20 30 40 10 20 30 40 ...Drösseln wir das wieder auf:
Startposition Inhalt Erklärung Variablenname im Speicher (nur im Code)* 0x10203040 0x00000004 Wert 4 a 0x10203044 0x00000005 Wert 5 b 0x10203048 0x10203040 Adresse von a pa 0x1020304C 0x10203040 Adresse von b pb* Der Variablenname existiert im laufenden Programm nirgends, und dient nur im Code als Bezeichner - Sprich als Hilfsmittel um es den Programmierer verständlich zu machen.
So, was passiert wenn du nun schreibst?
a = b; pa = pb;Konkret wird der Inhalt kopiert:
Startposition Inhalt Erklärung Variablenname im Speicher (nur im Code)* 0x10203040 0x00000004 Wert 4 a 0x10203044 0x00000004 Wert 4 b 0x10203048 0x10203040 Adresse von a pa 0x1020304C 0x10203040 Adresse von a pbWie du siehst haben nun jeweils die int-Werte den gleichen Inhalt, ebenso die beiden Zeiger. Dennoch sind es eigenständige Speicherbereiche.
Nun gehen wir einen winzigen Schritt weiter: Das deklarieren einer Variable muss (je nach Kontext) keine automatische Initialisierung bedeuten, ggf. wird einfach das als Wert angenommen was zufälligerweise im Speicher steht. Daher solltest du Variablen möglichst immer sobald möglich initialisieren, und Zeiger die noch kein Ziel haben, mit 0 initialisieren.
// ACHTUNG NICHT NACHMACHEN! int *pc; delete pc;Dies kann verheerende Auswirkungen haben (davon abgesehen das man nur Speicher mit delete freigeben sollte der mit new alloziert wurde), in pc kann irgendwas stehen, und delete greift versucht nun das an der Zieladresse befindliche Aufzuräumen. Glücklicherweise ist delete 0 definiert => dies führt nichts aus.
int *pc = 0; delete pc; // Wohldefiniert (delete 0 macht nichts)Nun gehen wir einen weiteren Schritt...
int * pd = new int(4); int * pe = pd; delete pe; pe = 0; delete pd; // KRASH!Was haben wir hier getan? Wenn du dir das bisherige durch den Kopf gehen lässt, solltest du wissen warum dies eine Zugriffsverletzung geben kann (undefiniert). Wie wir wissen haben pd und pe einen eigenen Inhalt. Wir kopieren nun den Inhalt (die Adresse den mit new allozierten Speichers) von pd in pe (Beide zeigen auf die gleiche Adresse). Nun geben wir den Speicher an der Zieladresse auf die pe zeigt frei, und setzen pe auf 0. Da pe aber einen eigenen Inhalt hat, wird pd davon nichts mitbekommen. Sprich das delete auf pd wird versuchen den bereits freigegebenen Speicher erneut freizugeben... undefiniertes Verhalten.
SOOOOOOOOO, nun Referenzen...
Tja, was sind Referenzen...
int a = 4; int &b = a;Was der Compiler hieraus hinter den Kulissen macht, kann ich nicht eindeutig sagen. Wichtig ist aber: Referenzen sind sogenannte "Aliasnamen". Referenzen sind also nur ein anderer Name für eine bestehende Variable.
int a = 4; int &b = a; b = 5; // Da b nur ein Aliasname für a ist... std::cout << a; // ... ist die Ausgabe hier nun 5!Es kann sein, das der Compiler hierbei nur eine Speicheradresse verwendet, du kannst a nun unter a als auch b erreichen und ändern.
Damit kommen wir zum nächsten Punkt:
Call-By-Value, Call-By-Referenz...#include <iostream> void foo1(int a) // Call by Value (Wertkopie) { std::cout << a << ", "; a = 1; std::cout << a << ", "; } void foo2(int & a) // Call by Referenz { std::cout << a << ", "; a = 2; std::cout << a << ", "; } int main() { int a = 0; std::cout << a << ", "; foo1(a); std::cout << a << ", "; foo2(a); std::cout << a << ", "; }Die Ausgabe dieses Programmes wird 0, 0, 1, 0, 0, 2, 2 sein.
Wenn du nun Verstehst warum das so ist, hast du meine Ausführung schon gut verstanden.Was ist wiederum mit einer Zeigerübergabe?
Das kommt darauf an was du in einer Funktion veränderst. Zeiger werden wie Werte nur kopiert, Zeigen aber auf den gleichen Inhalt.#include <iostream> void foo1(int * pa) { std::cout << *pa << ", "; // Würde 0 ausgeben int b = 1; pa = &b; // Änderungen des Zeiger-"Wertes" des Parameters, nicht des // Bereiches auf dem das Original zeigt. std::cout << *pa << ", "; } void foo2(int * pa) { std::cout << *pa << ", "; *pa = 2; // Änderung des Wertes auf den pa zeigt, nicht der Adresse std::cout << *pa << ", "; } int main() { int a = 0; std::cout << a << ", "; foo1(&a); std::cout << a << ", "; foo2(&a); std::cout << a << ", "; }Die Ausgabe wäre identisch wie oben. (Ich hoffe aufgrund meiner Müdigkeit keinen Flüchtigkeitsfehler gemacht zu haben, da ungetestet).
Es gibt auch Doppelzeiger int ** a; (Zeiger auf einen Int-Zeiger), Zeigerreferenzen int *& a; (Referenz/Aliasname einer Int-Zeiger Variablen)... usw. Aber das führt zu weit.
cu André
-
Siassei schrieb:
(...) die letzten Unsicherheiten (...)
ROFL

-
Servus,
man schlaft hier auch mal wer?
@miep Du hast natürlich Recht. Ich meinte cout << bar3.a
(Ich hoffe aufgrund meiner Müdigkeit keinen Flüchtigkeitsfehler gemacht zu haben, da ungetestet).
Der ein oder andere ist dabei, aber das macht nichts

Doppelzeiger:
int val = 5; size_t *ptr = &val; // Zeiger auf ein Element size_t **ptr2 = &ptr; // Zeiger auf ein Element des Zeigers ptr size_t ***ptr3 = &ptr2; // Zeiger auf ein Element des Zeigers ptr über den Zeiger ptr2 size_t ....Zeigerreferenzen:
#include <iostream> void ptrTest(int *&ptr) { *ptr = new int(5); } int main() { int *valPtr; ptrTest(valPtr); std::cout << valPtr; // Adresse des Pointers std::cout << *valPtr; // Wert des Ziels delete valPtr; // Speicherbereinigung }Alles ungetestet und mit dem ein oder anderem Flüchtigkeitsfehler

Gruß,
Thomas