Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

Anonymous

unregistriert

1

01.05.2004, 23:56

Vermeidung von Speicherlecks bei delete/Strukturen

Hallo,

ich bin z.Z. etwas verwirrt, was diese Thematik angeht, ich hoffe ich kann mein Problem einigermaßen gut beschreiben:

Ist es richtig, dass von an Funktionen übergebene Objekte kopien angelegt werden, oder trifft dies nur auf einfache Datentypen (integer, float..) zu? Was passiert, wenn ich eine Struktur anlege (ohne new, ich erstelle nur die Variable, auf dem Stack) und übergebe sie einer Funktion - wird diese Struktur dann kopiert? Man stelle sich mal vor, ich würde einer Listen-Klasse diese Struktur übergeben (das Objekt selbst, kein Zeiger darauf) und dann gelange ich aus dem Block, in dem die Strukur erstellt wurde, heraus, sodass die Struktur ja eigentlich vom Stack entfernt wird. Wenn bei der Übergabe der Struktur eine Kopie erstellt wurde, dann befindet sie sich immer noch in meiner Liste, falls nicht, dann müsste die Struktur ja jetzt nicht mehr vorhanden sein.

Weiterhin würde ich gerne wissen: Nehmen wir immer noch eine Listen-Klasse an und ich füge dieser Liste eine Struktur hinzu. Innerhalb dieser Struktur gibt es ja nun weitere Elemente - z.B. ein array. Wenn ich nun diese Struktur von meiner Liste entferne (mit delete das Listenelement löschen), muss ich erst alle Komponenten der Struktur löschen bevor ich die Struktur selbst mit delete lösche, oder werden diese Komponenten der Struktur, da sie ohne Pointer sind, also Objekte auf dem Stack darstellen, quasi automatisch mitgelöscht?

Ich bin momentan etwas durcheinander, und hoffe nicht, dass ich mir in meinem Programm hier und da Speicherlecks eingehandelt habe.
Ich hoffe ich konnte mein Problem zum Ausdruck bringen, und jemand könnte mir da weiter helfen.

Vielen Dank!

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

2

02.05.2004, 00:05

eine struktur an eine funktion per wert anstatt als zeiger oder referenz zu übergeben, is nicht gerade empfehlenswert, da diese dann, wie du bereits erkannt hast, kopiert wird, was nicht grad performant is.
allerdings wird nach der funktion eben nur diese kopie vernichtet und nicht das original.

zur 2. frage.
wenn du was mit delete löscht, dann isses schätze mal nicht auf dem stack sondern auf dem heap erstellt worden ( mit new ).
und ja alle elemente der classe/stuktur werden logischerweise mit gelöscht. wenn diese elemente zeiger sind, dann werden allerdings nur die zeiger gelöscht und nicht auch noch dass auf das sie zeigen.
solche zeiger stellen hier ein problem dar und sollten daher im destruktor der classe gelöscht werden ( inkl. dem speicher auf den sie verweisen ), da der bei aufruf von delete autom. ausgeführt wird.

Anonymous

unregistriert

3

02.05.2004, 00:13

Bin in dem gebiet zwar kein profi aber ich schau ma ob ich helfen kann ^^

Also, wie du schon gesagt hast wird (meines erachtens) eine Kopie angelegt da du ja keine Adresse an die Funktion übergibst sondern das Objekt selbst. Demzufolge befindet sich das Objelt 2 mal im Speicher. wenn nun der Geltungsbereich des "originals" am ende ankommt wird dieser natürlich gelöscht. Da beide (original und kopie) unterschiedliche speicheradressen bekommen haben musst du natürlich auch schaun wo deine Listen hinzeigen. Beachte jedoch das durch das übergeben des Objekts die Kopie innerhalb der Funktion Lokal ist und somit am ende der Funktion gelöscht wird. Es verhält sich also wie jeder andre Datentyp auch.

Das selbe gilt wenn du ein Array oder so von einem Datentyp anlegst. Sobald der Geltungsbereich ausläuft (was ja z.B. eine "übergeordnete" klasse die das array oder objekt einer andren klasse enthält wäre) wird auch alles was sich darin befindet gelöscht (solang du nicht nur die Adresse davon hast... sonst würde ja nur die Adresse gelöscht).

So, ich hoffe mal ich habs halbwegs verständlich rüber gebracht und hab hier nicht völligen Müll erzählt ^^

4

02.05.2004, 02:46

Es ist eigentlich recht einfach. Wenn du ein Objekt einer Funktion übergibst, dazu aber weder Zeiger noch Referenz verwendest, wird auf dem Stack eine kopie angelegt. Aber nicht irgend eine kopie. Die kopie wird mit Hilfe des Kopierkonstruktors angelegt. Wenn du keinen anlegst wird einer vom Compiler erstellt. Dann haste aber ein Problem. Da dieser nur eine Flache Kopie erstellt und keine Tiefe.
Wenn also im Destruktor alle Listenelemente gelöscht werden, heißt das, da alle Elemente am Ende der Funktion gelöscht werden. Wichtig dabei ist jedoch das dein Original davon nichts mitbekommt und denkt alle Elemente sind noch da.

Es ist also sehr wichtig die Konstruktoren zu Implementieren und zu wissen was passiert wenn ein Objekt bei Eintritt und Austritt eines Namensbereiches passiert. Zur Info. Eine Funktion stellt wie das Schlüsselwort namespace und die Bildung einer Klasse/Struktur ein Namensraum dar.

Was deine zweite Frage angeht. Die Antwort ist nicht pauschal beantwortbar. Aus dem einfachen Grund, es ist Unterschiedlich ob man eine Flache kopie hat oder eine Tiefe. Bei einer Flachen kopie, arbeitet man mit den Originallisten Elementen. Bei einer Tiefen jedoch nicht. Hier liegt eine eigenständige Liste vor.
Wichtig! Ich übernehme keinerlei Verantwortung für eventl. Datenverlust oder Schäden am Rechner ;D

Anonymous

unregistriert

5

02.05.2004, 14:01

Vielen Dank für die Antworten, sie haben mir schon sehr weitergeholfen.

Ich rekapituliere mal meine Erkenntnisse:

Ich habe eine Liste (einface verkettete Liste) implementiert. Als Listenelement setze ich nun ein Zeiger auf eine Struktur. Diese Struktur beinhaltet 2 Dinge: einen Zeiger auf ein Objekt, sowie ein Objekt selbst.

Will ich nun die Liste löschen, sowie alle Listenelemente und alles, was sich darin befindet, muss ich darauf achten, dass ich auch die Strukturen, also Listenelemente freigebe (da sie ja nur Zeiger auf Objekte im Heap sind). Jedoch befindet sich ja in der Struktur jeweils auch noch 1 Zeiger auf ein Objekt, welche ich dann zuvor auch noch freigeben/deleten muss, damit auch nichts übrig bleibt. Das 2. Objekt in der Struktur muss ich nicht freigeben, da es das Objekt selbst und kein Zeiger ist.

Hoffe es war so richtig...

@DragonMaster:

Danke für den Hinweis über die flachen Kopien und die Kopier-Konstruktoren. Ich habe dies nun alles einmal nachgelesen und es mir verständlich gemacht.

Anonymous

unregistriert

6

04.05.2004, 11:26

Zitat


Es ist eigentlich recht einfach. Wenn du ein Objekt einer Funktion übergibst, dazu aber weder Zeiger noch Referenz verwendest, wird auf dem Stack eine kopie angelegt. Aber nicht irgend eine kopie. Die kopie wird mit Hilfe des Kopierkonstruktors angelegt. Wenn du keinen anlegst wird einer vom Compiler erstellt. Dann haste aber ein Problem. Da dieser nur eine Flache Kopie erstellt und keine Tiefe.


Hm, hat denn eine Struktur auch einen Kopierkonstruktor?

Nehmen wir an, meine Struktur(en) sieht/sehen so aus:

Quellcode

1
2
3
4
5
6
7
8
9
10
11
struct SDefProperty
{
    CString strKey;
    CString strValue;
};

struct SNode
{
    CString strName;
    SDefProperty sProperty;
};


Man kann also sehen, dass ich keinen Zieger in den Strukturen verwende. CString soll hier als Beispiel eine eigene String Klasse sein, welche -keinen- selbst geschriebenen Kopierkonstruktor besitzt (Allerdings ist der Operator = überaladen, um sowas möglich zu machen: CString test = "hallo", ich weiss nicht, ob das vergleichbar ist?)

Was ist nun, wenn ich eine SNode Struktur (und zwar die Struktur selbst "SNode sNode;", keinen Zeiger) an eine Methode übergebe. Es wird also eine Kopie erstellt - aber eine Flache Kopie? Man kann doch aber keiner Struktur einen Kopierkonstruktor geben - aber ist das in diesem Fall denn nötig, da innerhalb der beiden Strukturen ja keine Zeiger sind, sondern immer die Objekte selbst?

Um das ganze nochmal zu verdeutlichen:

Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void MeineMethode()
{
    // 1. Struktur
    SDefProperty sProp;
    sProp.strKey = "Key";
    sProp.strValue = "Value";
    
    // 2. Struktur, welche die 1. kapselt
    SNode sNode;
    sNode.strName = "Alex";
    sNode.sProperty = sProp;
    
    // 2. Struktur einer Liste hinzufügen
    MeineListe->add( sNode );
}


Habe ich nun ein Problem beim Austritt aus der Methode "MeineMethode", da sProp und sNode dann flöten gehen, oder wurde bei "MeineListe->add( sNode );" eine Kopie der Struktur(en) erstellt?
(Wegen der Liste: intern wird durch ->add(..) ein neues Listenelement mit new erstellt, und der Wert des Listenelements wird gleich sNode gesetzt, also "elemValue = value;", wobei elemValue vom Typ SNode ist, und value hier das der add Methode übergebene Objekt sNode ist).

Ich hoffe, mir kann hier jemand ein wenig die Augen öffnen...

Vielen Dank nochmals!

7

04.05.2004, 15:34

1) Eine Struktur unterscheidet sich nur bei der Vergabe der Rechte von einer Klasse. Sonst sind beide absolut gleich.

2) Du hast ein kleines Problem in deinem Beispiel. Und zwar werden alle deine Strings verloren gehen. Weil du deiner Stringklasse keinen Kopierkonstruktor spendiert hast. Normalerweise ist die String länge in der Klasse variabel und nicht statisch. Der Compiler erzeugt jetzt einen eigenen Kopierkonstruktor, der jedoch nur eine Flache kopie erstellt.

Der Kompiler muss für Objekte (Strukturen/Klassen) einen Kopierkonstruktor erzeugen, damit dein Funktionsaufruf auch klappt. Denn genau an dieser Stelle wird der aufgerufen. Einen Memory Leak haste nicht. Dafür aber einen Memory Reading Error :)

Das beste wird sein wenn du dir noch einmal ein C++ Buch zu gemühte führst und dir die Konstruktoren und dessen bedeutung noch einmal genau durchliest. Es sollte in jedem nochso schlechten Buch stehen, da es eine sehr Elementare und Wichtige Sache ist.

Der Kopierkonstruktor und der Zuweisungsoperator machen zwar das selbe, werden daber an verschiedenen Stellen eingesetzt. Sie ersetzen sich damit also nicht.
Wichtig! Ich übernehme keinerlei Verantwortung für eventl. Datenverlust oder Schäden am Rechner ;D

Anonymous

unregistriert

8

04.05.2004, 17:37

Ja es ist wahr, ich habe mir die Kapitel über die Konstrukturen eben nochmals durchgelesen und nachvollzogen. Trotzdem komme ich nicht weiter :/(

Es geht ja nur indirekt um diese Sache - mein eigentliches Ziel ist es, eine Baumstruktur aufzubauen, und zwar mit 2 Listen - also als Element der 1. Liste nehme ich eine weitere Liste, so habe ich einen Baum.

Du sagtest, ich bekomme einen Read-Error, das ist auch so, und zwar genau beim Austritt aus der Methode. Also hier nochmal das Hinzufügen:

Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Root - Knoten erstellen
SDefProperty sProperty;
ZeroMemory(&sProperty, sizeof(SDefProperty));
sProperty.id = 123;

SNode sNode1;
sNode1.sProperty = sProperty;

CList<SNode> knoten;
knoten.add(sNode1);

pTree->add( knoten );

return;


Ich habe mal die Strings weggelassen, damit das ganze nicht zu unübersichtlich wird. Der Fehler kommt auch ohne die Strings und zwar anscheinend beim Zerstören der Listen. (denn "knoten" wird ja am ende der Methode zerstört).

Hier nochmal die Strukturen, wie sie jetzt aussehen:

Quellcode

1
2
3
4
5
6
7
8
9
struct SDefProperty
{
    int id;
};

struct SNode
{
    SDefProperty sProperty;
};


Die ganze Struktur ist also nun sehr einfach gehalten. Trotzdem kommt folgender Fehler, sobald der Programmablauf das oben genannte "return" erreicht:

Quellcode

1
2
3
4
Debug Assertion Failure!
File: dbgdel.cpp
Line: 51
Expression: _BLOCK_TYPE_IS_VALID(pHead->nBlockUse)


Beim schrittweisen Debuggen stellt sich raus, dass der Fehler beim Löschen eines Listenelementes auftritt - und zwar in der "inneren" Liste, also ein Knoten des Baumes. Meine Listenklasse besitzt eine Methode "clear" die im Destruktor der Liste aufgerufen wird. Diese Methode löscht alle Elemente der Liste.

Moment - jetzt habe ich eben selbst eine Idee =) Meine Listenklasse besitzt ja keinen Kopier-Konstrukor. Wenn ich nun der "äußeren Liste" die innere Hinzufüge (in der Zeile "pTree->add( knoten );") dann wird ja eine flache Kopie der Liste erstellt.

Ih frage mich nur, warum dann die clear Methode der inneren Liste Probleme macht.


Hm, ich bin z.B. ziemlich verzweifelt was das angeht. Ich hoffe wirklich, Du hast da noch ein paar Ideen für mich - falls ich Dich mit diesem Thema nerve, dann tut es mir leid. Natürlich darf ich von niemandem erwarten, sich mit einem solch blöden fremden Problem zu beschäftigen, wie ich es gerade habe.


Beste Grüsse,
Alex

Nox

Supermoderator

Beiträge: 5 272

Beruf: Student

  • Private Nachricht senden

9

04.05.2004, 17:48

Und wie sieht deine Add/Clear Funktion aus? Außerdem übergibt man zum Adden eigentlich immer die Addresse.

10

04.05.2004, 20:03

Das Thema ist nicht blöde. Ganz im Gegenteil. So zimlich jeder der mit C++ Anfängt und noch nie mit Konstruktoren gearbeite hat, dürfte ähnliche Probleme haben.

Du must dir klar machen was in deinem Code passiert. Nimm ihn auseinander. Stück für Stück. Du erzeugst eine Instanz des Typs CList. Diese hat keinen eigenen Kopierkonstruktor. Diese Instanz übergibst du jetzt der List pTree (nehme an die ist Global). Was passiert hier. Da du keine Referenzen und Pointer verwendest wird genau hier eine kopie von deiner Instanz erzeugt. Aber nur eine Flache. Am ende der Methode pTree->add(knoten); wird diese Instanz wieder gelöscht. Sprich dessen Destruktor wird aufgerufen. Damit werden alle einträger dieser Liste gelöscht. CList<SNode> knoten bekommt davon jedoch nichts mit und wird dann am ende (bei return) ebenfalls gelöscht. Sie geht aber davon aus das alle Objekte noch da sind.
Ergebnis ist der Memory Read Error.

Du must dir folgendes merken. Wenn du in Objekten Zeiger Definierst wirst du auch einen Kopierkonstruktor erstellen müssen. Wenn du keine Zeiger verwendest ist es egal.
Klassen/Strukturen sind nicht einfach nur Objekte um mehrer Daten zusammen zu fassen. Es sind Objekte um auch einen regulären Typ (wie char oder int) zu erstellen. Wenn dir das klar ist, wirst du dich selbst fragen, was passiert wenn ich das eine oder andere mache?

[edit]Noch ein Tipp: Als ich angefangen habe, hab ich mir einfach mal ein kleine Testklasse erstellt und alle Konstruktoren Implementiert. So das sie auf der Konsole eine ausgabe machen. Mit einem kleinen Testprogramm habe ich dann einfach Operationen ausgeführt und geschaut wo welcher Konstruktor aufgerufen wird.
Vieleicht Hilft dir das etwas weiter.[/edit]
Wichtig! Ich übernehme keinerlei Verantwortung für eventl. Datenverlust oder Schäden am Rechner ;D

Werbeanzeige