 |
C++Talk.NET C++ language newsgroups
|
| View previous topic :: View next topic |
| Author |
Message |
Falco Meurer Guest
|
Posted: Sat Oct 16, 2004 2:34 pm Post subject: Warum Absturz? |
|
|
Hallo,
folgender Programmteil stürzt bei mir ab, und ich suche schon seit Stunden
nach dem tatsächlichen Grund:
**
#include <iostream>
class Path
{
public:
int* way;
int n;
Path(int num);
~Path();
};
Path::Path(int num)
{
n = num;
way = new int[num];
}
Path::~Path()
{
delete [] way;
}
class FTreeNode
{
public:
FTreeNode() {sibling=NULL;}
FTreeNode* sibling;
};
class FTree
{
public:
FTree() {RootNode=NULL;}
FTreeNode* GetNodeFromPath(Path p);
FTreeNode* AddNode(Path p);
FTreeNode* RootNode;
};
FTreeNode* FTree::GetNodeFromPath(Path p)
{
return RootNode;
}
FTreeNode* FTree::AddNode(Path p)
{
FTreeNode* temp = GetNodeFromPath(p);
while (temp->sibling != NULL) temp = temp->sibling;
temp->sibling = new FTreeNode;
return temp->sibling;
}
int main(int argc, char *argv[])
{
FTree Tree;
Tree.RootNode = new FTreeNode;
for (int x = 0; x < 1000; x++) {
printf("%d ", Tree.AddNode( Path(0) ) );
}
system("PAUSE");
return 0;
}
**
Ich vermute, dass es etwas mit der Konstruktion der Klasse "Path" zu tun
haben muss, denn wenn ich in Konstruktor/Destruktor die Variable "way" nicht
mit new initialisiere, kommt es zu keinem Absturz.
Oder, wenn ich in der Funktion "AddNode" den Aufruf "GetNodeFromPath(p)"
direkt mit "RootNode" ersetze, passiert auch nichts.
Hat es vielleicht etwas mit Kopierkonstruktor zu tun?
Danke im Voraus,
Falco
--
de.comp.lang.iso-c++ - Moderation: mailto:voyager+mod (AT) bud (DOT) prima.de
FAQ: http://www.voyager.prima.de/cpp/ mailto:voyager+send-faq (AT) bud (DOT) prima.de
|
|
| Back to top |
|
 |
Thomas Mang Guest
|
Posted: Sat Oct 16, 2004 6:14 pm Post subject: Re: Warum Absturz? |
|
|
"Falco Meurer" <falco.meurer (AT) fernuni-hagen (DOT) de> schrieb im Newsbeitrag
news:ckrbbi$6gl$1 (AT) beech (DOT) fernuni-hagen.de...
| Quote: | Hallo,
folgender Programmteil stürzt bei mir ab, und ich suche schon seit Stunden
nach dem tatsächlichen Grund:
**
#include <iostream
class Path
{
public:
int* way;
int n;
Path(int num);
~Path();
};
|
warum sind die Datenmember nicht private?
| Quote: |
Path::Path(int num)
{
n = num;
way = new int[num];
}
|
Warum verwendest Du nicht die Initialisierungsliste?
| Quote: |
Path::~Path()
{
delete [] way;
}
|
Bei deiner Klasse machen der Compiler-generierte Kopierkonstruktor und
operator= was falsches, nämlich eine direkte Kopie/Zuweisung der
Datenmitglieder.
Im Fall von n macht das nichts, bei way ist es schlimm.
Stell Dir folgendes vor:
Path a(100);
Path b(a);
Jetzt alloziiert der Konstruktor von a 100 ints. Fein. Dann wird b als Kopie
von a erstellt. b.n ist dann ebenfalls gleich 100, aber b.way zeigt auf
dieselbe Adresse wie a.way. Zwei Zeiger, die auf die gleiche Adresse zeigen.
Soweit passiert noch nichts. Aber.....
Jetzt werden die Destruktoren aufgerufen, und zwar in umgekehrter
Reihenfolge. Zuerst wird b zerstört und der Speicher, auf den b.way zeigt
freigegeben. Dann wird a zerstört, und sein Destruktor ruft ebenfalls den
operator delete[] für way auf. Doch dieser Speicher wurde bereits
freigegeben, nämlich im Destruktor von b (da ja beide Zeiger auf dieselbe
Adresse zeigen). Deshalb krachts.
Die Lösung ist
a) Eine "tiefe Kopie", das heißt Du schreibst Deinen eigenen
Kopierkonstruktor / operator=, wo way auf neu alloziierten Speicher gesetzt
wird und die ints hineinkopiert werden
b) Einfacher: Du verwendest std::vector
operator= das richtige machen. In dem Fall brauchst Du für Deine Klasse
keinen eigenen Kopierkonstruktor / operator= schreiben, da die
Compiler-generierten Versionen das richtige machen.
| Quote: |
class FTreeNode
{
public:
FTreeNode() {sibling=NULL;}
|
a) Initialisierungsliste verwenden.
b) vermeide NULL. Das verwendet man in C, da dort die
pointer-Umwandlung-Semantik anders ist. In C++ verwendet man 0.
| Quote: | FTreeNode* sibling;
};
|
Wieder: Datenmember sollten private sein.
| Quote: |
class FTree
{
public:
FTree() {RootNode=NULL;}
|
Wie oben.
| Quote: | FTreeNode* GetNodeFromPath(Path p);
FTreeNode* AddNode(Path p);
|
Warum wird Path nicht per const-Referenz übergeben?
| Quote: | FTreeNode* RootNode;
};
FTreeNode* FTree::GetNodeFromPath(Path p)
{
return RootNode;
}
|
Du verwendest den Parameter p gar nicht. Dann entferne ihn. Außerdem scheint
mir die Funktion, gemessen an dem was sie macht, nicht den passenden Namen
zu tragen.
| Quote: |
FTreeNode* FTree::AddNode(Path p)
|
Da GetNodeFromPath nun keinen Path mehr als Parameter empfängt, braucht
diese Funktion auch keinen mehr.
| Quote: | {
FTreeNode* temp = GetNodeFromPath(p);
while (temp->sibling != NULL) temp = temp->sibling;
temp->sibling = new FTreeNode;
return temp->sibling;
}
|
Aha, eine einfach verkettete Liste.
Dann braucht Deine Klasse aber auch einen Destruktor, der die einzelnen
Knoten wieder freigibt.
Außerdem fehlt ein Konstruktor, der RootNode auf 0 stellt. Dann mußt Du
logischerweise noch Deine while-Bedingung so anpassen, daß RootNode 0 sein
kann.
Und selbstverständlich mußt Du eigene Versionen vom Kopierkonstruktor /
operator= schreiben, da sonst dasselbe Problem auftritt wie bei Path.
| Quote: |
int main(int argc, char *argv[])
{
FTree Tree;
Tree.RootNode = new FTreeNode;
for (int x = 0; x < 1000; x++) {
printf("%d ", Tree.AddNode( Path(0) ) );
}
|
a) man sollte die prä-Inkrement Version der Post-Inkrement vorziehen.
b) printf ist C. In C++ verwendet man std::cout und operator<<.
| Quote: |
system("PAUSE");
return 0;
}
**
Ich vermute, dass es etwas mit der Konstruktion der Klasse "Path" zu tun
haben muss, denn wenn ich in Konstruktor/Destruktor die Variable "way"
nicht
mit new initialisiere, kommt es zu keinem Absturz.
Oder, wenn ich in der Funktion "AddNode" den Aufruf "GetNodeFromPath(p)"
direkt mit "RootNode" ersetze, passiert auch nichts.
Hat es vielleicht etwas mit Kopierkonstruktor zu tun?
|
Ja, mit dem Kopierkonstruktor von Path.
Noch zwei Bemerkungen:
Dein aktuelles Design ist extrem fehleranfällig für Benutzer. Bitte, lerne
und beherrsche "encapsulation".
Es gibt in der Standardbibliothek Container, z.B. std::list. Die machen
genau das, was Du nachbastelst, nur sicher effizienter, fehlerfrei (sollte
man doch zumindest hoffen) und (hoffe ich ebenfalls) exception-sicher.
Thomas
--
de.comp.lang.iso-c++ - Moderation: mailto:voyager+mod (AT) bud (DOT) prima.de
FAQ: http://www.voyager.prima.de/cpp/ mailto:voyager+send-faq (AT) bud (DOT) prima.de
|
|
| Back to top |
|
 |
Thomas Maeder Guest
|
Posted: Sat Oct 16, 2004 6:22 pm Post subject: Re: Warum Absturz? |
|
|
"Falco Meurer" <falco.meurer (AT) fernuni-hagen (DOT) de> writes:
| Quote: | folgender Programmteil stürzt bei mir ab, und ich suche schon seit Stunden
nach dem tatsächlichen Grund:
|
Ich sehe verschiedene Gründe.
| Quote: | #include <iostream
class Path
{
public:
int* way;
int n;
Path(int num);
~Path();
};
|
1. public data members
| Quote: | Path::Path(int num)
{
n = num;
way = new int[num];
}
|
[Für die Initialisierung von Members gibt es in C++ die
Memberinitialisierungsliste.]
| Quote: | Path::~Path()
{
delete [] way;
}
|
2. Kopierkonstruktor und Kopierzuweisungsoperator der Klasse Path verhalten
sich nicht so, wie es der oben gezeigte Konstruktor und dieser Konstruktor
verlangen. Sie kopieren Member für Member, und spätestens bei der Destruktion
einer von mehreren Kopien hat das Programm undefiniertes Verhalten.
-> private deklarieren und/oder richtig implementieren.
| Quote: | class FTreeNode
{
public:
FTreeNode() {sibling=NULL;}
|
[Für die Initialisierung von Members gibt es in C++ die
Memberinitialisierungsliste.]
| Quote: | FTreeNode* sibling;
|
3. public data members
| Quote: | };
class FTree
{
public:
FTree() {RootNode=NULL;}
|
[Für die Initialisierung von Members gibt es in C++ die
Memberinitialisierungsliste.]
| Quote: | FTreeNode* GetNodeFromPath(Path p);
FTreeNode* AddNode(Path p);
FTreeNode* RootNode;
|
4. public data members
| Quote: | };
FTreeNode* FTree::GetNodeFromPath(Path p)
{
return RootNode;
}
|
[Wozu dient der Parameter p?]
| Quote: | FTreeNode* FTree::AddNode(Path p)
{
FTreeNode* temp = GetNodeFromPath(p);
while (temp->sibling != NULL) temp = temp->sibling;
temp->sibling = new FTreeNode;
return temp->sibling;
}
|
[Wozu dient der Parameter p?]
--
de.comp.lang.iso-c++ - Moderation: mailto:voyager+mod (AT) bud (DOT) prima.de
FAQ: http://www.voyager.prima.de/cpp/ mailto:voyager+send-faq (AT) bud (DOT) prima.de
|
|
| Back to top |
|
 |
Thomas Mang Guest
|
Posted: Sat Oct 16, 2004 8:37 pm Post subject: Re: Warum Absturz? |
|
|
"Thomas Mang" <nospam (AT) nospam (DOT) prima.de> schrieb im Newsbeitrag
news:41716517$0$12646$3b214f66 (AT) usenet (DOT) univie.ac.at...
| Quote: |
class FTreeNode
{
public:
FTreeNode() {sibling=NULL;}
a) Initialisierungsliste verwenden.
b) vermeide NULL. Das verwendet man in C, da dort die
pointer-Umwandlung-Semantik anders ist. In C++ verwendet man 0.
|
Ich glaube, da habe ich mich unklar ausgedrückt:
Laut aktuellem Standard kann man durchaus NULL verwenden, da der eine
Definition a là
#define NULL (void*) 0
nicht zuläßt, sondern jeden null-pointer repräsentieren muß. Die
unterschiedliche Umwandlungssemantik zwischen C und C++ spielt theoretisch
keine Rolle.
Das Problem ist (bzw. besser war) eher praxisrelevant, da nicht jeder
Compiler sich daran hielt.
Thomas
--
de.comp.lang.iso-c++ - Moderation: mailto:voyager+mod (AT) bud (DOT) prima.de
FAQ: http://www.voyager.prima.de/cpp/ mailto:voyager+send-faq (AT) bud (DOT) prima.de
|
|
| Back to top |
|
 |
Horst Kraemer Guest
|
Posted: Sun Oct 17, 2004 8:53 am Post subject: Re: Warum Absturz? |
|
|
"Falco Meurer" <falco.meurer (AT) fernuni-hagen (DOT) de> wrote:
| Quote: | Hallo,
folgender Programmteil stürzt bei mir ab, und ich suche schon seit Stunden
nach dem tatsächlichen Grund:
**
#include <iostream
class Path
{
public:
int* way;
int n;
Path(int num);
~Path();
};
Path::Path(int num)
{
n = num;
way = new int[num];
}
Path::~Path()
{
delete [] way;
}
class FTreeNode
{
public:
FTreeNode() {sibling=NULL;}
FTreeNode* sibling;
};
class FTree
{
public:
FTree() {RootNode=NULL;}
FTreeNode* GetNodeFromPath(Path p);
FTreeNode* AddNode(Path p);
FTreeNode* RootNode;
};
FTreeNode* FTree::GetNodeFromPath(Path p)
{
return RootNode;
}
FTreeNode* FTree::AddNode(Path p)
{
FTreeNode* temp = GetNodeFromPath(p);
while (temp->sibling != NULL) temp = temp->sibling;
temp->sibling = new FTreeNode;
return temp->sibling;
}
int main(int argc, char *argv[])
{
FTree Tree;
Tree.RootNode = new FTreeNode;
for (int x = 0; x < 1000; x++) {
printf("%d ", Tree.AddNode( Path(0) ) );
}
system("PAUSE");
return 0;
}
**
Ich vermute, dass es etwas mit der Konstruktion der Klasse "Path" zu tun
haben muss, denn wenn ich in Konstruktor/Destruktor die Variable "way" nicht
mit new initialisiere, kommt es zu keinem Absturz.
Oder, wenn ich in der Funktion "AddNode" den Aufruf "GetNodeFromPath(p)"
direkt mit "RootNode" ersetze, passiert auch nichts.
Hat es vielleicht etwas mit Kopierkonstruktor zu tun?
|
Richtig analysiert.
Du rufst AddNode mit einer Parameter vom Typ Path auf, der per Path(0)
im Moment des Aufrufs generiert wird, dieses Path-Objekt wird dann
innerhalb von AddNode an GetNodeFromPath per *Kopie* weitergereicht.
Diese beiden Prameter-Instanzen von Path werden nun jeweils beim
Verlassen ihrer Funktion per ~Path zerstoert. Der
Default-Kopierkonstruktor kopiert aber nur bloed die Werte der
Elemente. D.h. der Zeiger way der Kopie von Path(0) wird direkt in der
Kopie fuer den Parameter von GetNodeFromPath uebernommen. Daher zeigen
beide auf dieselben Objekte und das Zerstoeren des AddNode-Parameters
gibt den bereits beim Zerstoeren des GetNodeFromPath freigegebenen
Speicherplatz erneut frei - was zu undefiniertem Verhalten fuehrt.
Du kannst dieses Problem zu Demonstrationszwecken beheben, wenn Du
FTree::GetNodeFromPath
nicht so
FTreeNode* FTree::GetNodeFromPath(Path p)
sondern so
FTreeNode* FTree::GetNodeFromPath(const Path &p)
deklarierst. Dann wird nicht eine Kopie des AddNode-Parameters,
sondern nur eine Referenz auf diesen an GetNodeFromPath uebergeben,
d.h. es wird physisch *dasselbe* Path-Objekt weitergereicht. Wenn am
Ende einer Funktion eine Referenz "stirbt", wird nichts zerstoert.
Wahrscheinlich ist auch sinnvoll den Parameter von AddNode mit einem
Parameter vom Typ (const Path &) zu deklarieren. Dein Programm
funktioniert dann trotzdem richtig, denn man kann auch dann
AddNode(Path(0));
sagen. Es wird on the fly ein Objekt Path(0) erzeugt und ein solches
"Luftobjekt" kann an eine Referenz auf ein const-Objekt uebergeben
werden. Wenn Du Objekte vom Typ Path nie *bewusst* kopieren moechtest,
brauchst Du auch keinen Kopierkonstruktor zu definieren. Das
"unbewusste" Kopieren findet nicht mehr statt, wenn Du Path-Objekte
immer nur per Referenz weitergibst (es sei denn, eine weitere Klasse,
die kopiert werden soll, enthaelt Path-Objekte oder Zeiger auf
Path-Objekte).
Du kannst auch erreichen, dass die Klasse Path nicht einmal
versehentlich kopiert werden kann, indem Du sie so
class Path
{
public:
int* way;
int n;
Path(int num);
~Path();
private:
Path(const Path&);
};
definierst und Path(const Path&) gar nicht implementierst. Dann meldet
Dir der Compiler bei einer versehentlichen Wertuebergabe eines
Path-Objekts (wie im Aufruf GetNodeFromPath(p) bei Deiner Version)
einen Fehler, weil der Kopierkonstruktor von Path zwar existiert, aber
wegen der private-Deklaration nicht zugaenglich ist und daher nicht
verwendet werden kann.
--
Horst
--
de.comp.lang.iso-c++ - Moderation: mailto:voyager+mod (AT) bud (DOT) prima.de
FAQ: http://www.voyager.prima.de/cpp/ mailto:voyager+send-faq (AT) bud (DOT) prima.de
|
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|