Heftiger Bug in RAD Studio 2007 mit auto_ptr und Delphi-Klassen?
-
Was wäre denn, wenn du bei der Erzeugung einen try/catch-Block um das new VCLDerived... machst und im catch-Block den Pointer auf 0 setzt oder eine std::exception wirfst?
-
Das würde das (ziemlich schwer zu reproduzierende) Problem vermutlich lösen, was ich auch schon auf diese Art und Weise umgebaut habe.
Das ist für mich aber eigentlich peripher, mich würde interessieren ob jemand Hintergrundwissen, Details, Gründe für diese Verhaltensweise kennt. Also es geht mir nicht um die Lösung, sondern um das Verständnis. Ich möchte kein Voodoo-Programming betreiben.
-
7H3 N4C3R schrieb:
Ist nicht böse gemeint, ich beantworte gerne Detailfragen. Aber nicht wenn das gefragte schon (ausreichend und eindeutig wie ich finde) im Beitrag steht.
Nein, dein Code ist weder ausreichend noch eindeutig. Unabhängig von deiner Zuweisung, ist es schon relevant was du noch damit machst, die gezeige Zeile kann eigentlich nicht der Grund für die Exception sein (Wenn dabei eine Exception auftritt wird kein Zeiger zurückgegeben, sondern zum nächsten zutreffenden catch-Block gesprungen, der auto_ptr wird daher garnicht erst nicht instanziert).
Typische Fehler beim auto_ptr sind für einen selbst unbemerkte Zuweisungen. Um ein Beispiel zu nennen: Wenn du ein Autopointer als Member benutzt, den Kopierkonstruktor nicht überschreibst und irgendwo das Objekt unbedacht kopierst (z.b. durch einen Funktionsaufruf mit Call-By-Value), kann dein Fall auftreten.
Wenn du Zugriff auf die Boost-Bibliothek hast, würde mich mal Interessieren wie das ganze mit boost::scoped_ptr oder boost::shared_ptr aussehen würde.
cu André
-
7H3 N4C3R schrieb:
Bitte meinen Beitrag lesen, da steht alles ohne Verheimlichungen drin, inklusive eines Codebeispiels in cpp-Tags, wie ich den auto_ptr benutze (nicht mehr und nicht weniger als exakt so, wie es dort steht).
Ist nicht böse gemeint, ich beantworte gerne Detailfragen. Aber nicht wenn das gefragte schon (ausreichend und eindeutig wie ich finde) im Beitrag steht.
Das kann ich bei mir nicht kompilieren... sei also bitte so nett und poste den minimalen Code, der deinen Fehler reproduziert.
Meiner Ansicht nach ist der gepostete Code alles andere als ausreichend, da er in keinem Kontext steht und überhaupt nicht klar ist, wie der auto_ptr nach der Konstruktion benutzt wird.
Da im Konstruktor des auto_ptr die exception geworfen wird, wird auch das auto_ptr Objekt nicht konstruiert und kann demnach auch nicht seinen Destruktor aufrufen. Ich vermute den Fehler daher irgendwo anders.Gruß,
Doc
-
7H3 N4C3R schrieb:
Das würde das (ziemlich schwer zu reproduzierende) Problem vermutlich lösen, was ich auch schon auf diese Art und Weise umgebaut habe.
Das ist für mich aber eigentlich peripher, mich würde interessieren ob jemand Hintergrundwissen, Details, Gründe für diese Verhaltensweise kennt. Also es geht mir nicht um die Lösung, sondern um das Verständnis. Ich möchte kein Voodoo-Programming betreiben.
Das Problem liegt höchstwahrscheinlich im Transfer of Ownership des gekapselten Pointers. Es kann für jeden auto_ptr, die den selben Pointer kapseln, nur ein einziges Besitzerobjekt geben. Sobald dieses Besitzerobjekt zerstört wird, zerstört es das Objekt, auf das es zeigt, ebenfalls. Sollten andere auto_ptr auf das gleiche Objekt zeigen sind sie nun ungültig, weil sie auf die Adresse eines zerstörten Objekts zeigen (dangling pointer).
Hier einige Beispiele, wie sich auto_ptr verhält:
[cpp]
#include <memory>using namespace std;
struct S
{
};void func( auto_ptr<S> ptr )
{
}void bang()
{
S* pS = new S();
auto_ptr<S> ap1( pS );
auto_ptr<S> ap2( pS );// ap1 und ap2 zeigen auf das gleiche Objekt, das beim Verlassen der Methode
// zwei Mal freigegeben wird -> undefined behaviour
}int main()
{
auto_ptr<S> ex1( new S() );
// ex1 ist Besitzer und zeigt auf eine Instanz von Sauto_ptr<S> ex2 = ex1;
// ex2 ist nun Besitzer von S und zeigt auf eine Instanz von S,
// ex1 ist jetzt ungültig und enthält einen NULL pointerfunc( ex2 );
// Beim Aufruf von f wird ein temporary erzeugt, das mit ex2 initialisiert
// wird. Wie im Beispiel 2 erfolgt ein Ownership Transfer von ex2 an das
// temporary, damit ist ex2 nun ungültig und hält einen NULL pointer
}
[/code]Hoffe, das hilft etwas weiter. Ansonsten kann ich ace nur zustimmen, schnapp dir die boost Bibliothek und benutze daraus die smart pointer, dann hast du damit keine Probleme mehr. Im übrigen bietet die boost Bibliothek auch noch andere wirklich gute Sachen an, es lohnt sich auf jeden Fall, sie sich mal anzugucken.
Gruß,
Doc
-
asc schrieb:
Nein, dein Code ist weder ausreichend noch eindeutig. Unabhängig von deiner Zuweisung, ist es schon relevant was du noch damit machst, die gezeige Zeile kann eigentlich nicht der Grund für die Exception sein (Wenn dabei eine Exception auftritt wird kein Zeiger zurückgegeben, sondern zum nächsten zutreffenden catch-Block gesprungen, der auto_ptr wird daher garnicht erst nicht instanziert).
Es ist keine Zuweisung, sondern eine Initialisierung. p ist eine lokale Variable (exakt so wie es im Originalpost steht). Die Exception tritt wie in meinem Originalpost schon erwähnt beim Erzeugen des konkreten Objekts in seinem Konstruktor gewollt auf.
Ich weiß auch, dass der auto_ptr nicht erzeugt werden sollte - aber wie im Originalpost geschrieben wird sein Destruktor trotzdem aufgerufen, obwohl die Exception, mit dem Debugger überprüft, im Konstruktor des konkreten Objekts auftritt. Sein Scope ist (bis auf einen Methodenaufruf, der nicht aufgerufen wird), danach explizit durch geschweifte Klammern zuende.asc schrieb:
Typische Fehler beim auto_ptr sind für einen selbst unbemerkte Zuweisungen. Um ein Beispiel zu nennen: Wenn du ein Autopointer als Member benutzt, den Kopierkonstruktor nicht überschreibst und irgendwo das Objekt unbedacht kopierst (z.b. durch einen Funktionsaufruf mit Call-By-Value), kann dein Fall auftreten.
Danke für die C++-Lehrstunde
Bei einer Zuweisung mit Call-By-Value könnte übrigens kein (nicht auf einfache Art und Weise) Fehler im Destruktor auftreten, da der Besitz des Zeigers trotzdem immer übertragen wird bei Compiler-generiertem Copy-Konstruktor, sofern dieser generierbar ist, oder aber er wird garnicht übertragen, wenn man einen eigenen geschrieben hat und vergessen zuzuweisen (oder man macht solche Schweinereien wie den Zeiger aus dem auto_ptr manuell einem zweiten zuzuweisen und wäre selber Schuld).
asc schrieb:
Wenn du Zugriff auf die Boost-Bibliothek hast, würde mich mal Interessieren wie das ganze mit boost::scoped_ptr oder boost::shared_ptr aussehen würde.
Habe ich leider nicht.
-
DocShoe schrieb:
Das Problem liegt höchstwahrscheinlich im Transfer of Ownership des gekapselten Pointers.
Entschuldige bitte meine Ausdrucksweise, ich bin arbeitstechnisch gerade eh schon leicht gereizt - aber ich bin nicht blöd. Ich weiß, dass man keine Ownership transferiert und weiß auch sonst, wie ein auto_ptr zu benutzen ist. Es ist eine einzige lokale Variable ohne Übertragung von Eigentümerschaft.
Ein minimalstes Beispiel, was außer Klammern und Kommentaren mein Originalbeispiel ist.
Eigenes Datamodule erzeugen, in einem anderen Formular (oder sonstwas) in z.B. einen Button->Click folgendes einfügen:
{ std::auto_ptr<TMyDatamodule> p( new TMyDatamodule (0 /*Owner*/)); // TMyDatamodule kann in bestimmten, sinnvollen Konstellationen eine Exception im Konstruktor werfen p->test(); // wird - sogar mit dem CPU-Monitor geprüft, nicht aufgerufen // hier steht absolut garnichts weiter } // Scope-Ende, hier scheppert es
CodeGuard hat im übrigen nichts zu meckern.
-
7H3 N4C3R schrieb:
Ich weiß auch, dass der auto_ptr nicht erzeugt werden sollte - aber wie im Originalpost geschrieben wird sein Destruktor trotzdem aufgerufen, obwohl die Exception, mit dem Debugger überprüft, im Konstruktor des konkreten Objekts auftritt. Sein Scope ist (bis auf einen Methodenaufruf, der nicht aufgerufen wird), danach explizit durch geschweifte Klammern zuende.
Wenn das so wäre, würde der BCB2007 wirklich einen Heftigen Bug haben.
Würdest du mit bitte sagen was folgendes Programm (hoffe ohne Fehler aus dem Gedächtnis geschrieben) für Ausgaben erzeugt?
#include <iostream> #include <memory> class A { public: A() { std::cout << "A::A() << std::endl; throw(1); } A(A const &) { std::cout << "A::A(A const &) << std::endl; } A& operator=(A const &) { std::cout << "A& A::operator=(A const &) << std::endl; return *this; } ~A() { std::cout << "A::~A() << std::endl; } }; class B { public: auto_ptr<A> a; B() : a(new A()) { std::cout << "B::B() << std::endl; } ~B() { std::cout << "B::~B() << std::endl; } }; int main() { try { B b; } catch(...) {} }
cu André
-
Das Programm erzeugt, wie es auch sollte nur:
A::A()
als Ausgabe.
Mein Verdacht liegt eher bei den VCL/Delphi-Klassen, da deren Konstruktion durch virtuelle Konstruktoren etwas anders funktioniert, als man es in C++ gewohnt ist.
Das "lustige" an diesem Fehler ist, dass er nur sehr schwer zu reproduzieren ist und, dass CodeGuard mit vollen Optionen nichts zu meckern hat. Eigentlich würde mich das sofort auf zerschossenen Speicher bringen, allerdings konnte ich in der Hinsicht weder selbst, noch Tool-gestützt etwas finden. Das gute, alte Purify kann allerdings mit den Debug-Infos vom bcc32 nix anfangen und macht wie's aussieht die Einsprungspunkte ins Speichermanagement kaputt, wodurch sich die Anwendung garnicht erst starten lässt.
-
Ich habe das gerade auch mal getestet. Dabei habe ich A von TComponent abgeleitet, so dass es eine richtige VCL-Klasse ist. Auch hier wird nur der Konstruktor aufgerufen.
-
Das funktioniert bei mir auch.
Ist zum Verzweifeln. Nur genau deswegen will ich diesen Fehler nicht einfach durch Code-umstellen entfernen - sofern er denn ganz weg ist und dann nicht nur seltener auftritt. Ich will andere Möglichkeiten auch nicht ausschließen, momentan sieht es allerdings nach auto_ptr-zusammenhängend aus. Wenn jemand noch einen gutes Tool á la Purify kennt das auch mit dem bcc32 funktioniert, ich wäre auch sehr interessiert.
-
Wie asc schon sagte könntest du es mit boost::shared_ptr versuchen. Das ist ja eine Header-only-Klasse so dass du keine Libs erzeugen musst. Zusammenarbeit mit den BCBs ist auch kein Problem. Evtl. ja nur mal zum Test.
-
Naja, das wäre aber wiederum die Art zu Debuggen, wie ich sie eigentlich vermeiden will. Also so lange am Code ändern, bis der Fehler nicht mehr auftaucht, ohne tatsächlich verstanden zu haben woran es liegt. Vielleicht geht es dann und bei der nächsten Änderung, die meinetwegen den Stackframe etwas anders aussehen lässt, tritt der gleiche Fehler wieder zutage.
Immerhin scheint die Verwendung von CodeGuard den Fehler etwas öfter zu provozieren, was doch wieder von auto_ptr wegdeutet. Ich liebe solche Fehler
-
Hallo,
7H3 N4C3R schrieb:
Ein minimalstes Beispiel, was außer Klammern und Kommentaren mein Originalbeispiel ist.
Eigenes Datamodule erzeugen, in einem anderen Formular (oder sonstwas) in z.B. einen Button->Click folgendes einfügen:
{ std::auto_ptr<TMyDatamodule> p( new TMyDatamodule (0 /*Owner*/)); // TMyDatamodule kann in bestimmten, sinnvollen Konstellationen eine Exception im Konstruktor werfen p->test(); // wird - sogar mit dem CPU-Monitor geprüft, nicht aufgerufen // hier steht absolut garnichts weiter } // Scope-Ende, hier scheppert es
Wenn ich das Minimalbeispiel in C++Builder 6 oder 2006 zu reproduzieren versuche, funktioniert alles einwandfrei.
Mein Datenmodul wirft im Konstruktor eine Exception, wenn der übergebene Parameter 0 ist. Dabei habe ich beide Exception-Typen (C++-Exception, Delphi-Exception) getestet, und in beiden Fällen wird der Destruktor von std::auto_ptr nicht aufgerufen, das Objekt vielmehr gar nicht konstruiert.
Könntest du evtl. dein Minimalbeispiel als vollständiges Projekt irgendwo hochladen (Rapidshare o.ä.)?
-
Auf ein Minimalbeispiel reduziert funktioniert es bei mir auch wunderbar. Breakpoints auf auto_ptr halten nicht an, selbst ein Trace im Disassembler läuft niemals den auto_ptr an. Kann eigentlich nur heißen, dass es entweder richtig funktioniert und irgendwas anderes bösartig zersägt ist oder das Problem nur unter ganz bestimmten Umständen zu Tage tritt.
Dass das sonst anscheinend noch niemandem weiter passiert ist, spricht wohl gegen einen Bug im RAD Studio.Also nochmal meine Bitte, wenn jemand ein Tool wie Purify kennt, was mit den Debug Infos vom Builder zurecht kommt und nicht die Einsprungadressen in den Memorymanager umlegt, wäre mir das sehr willkommen. Der CodeGuard ist zumindest selbst nicht imstande irgendeinen Fehler zu finden. Der BoundsChecker ist ja anscheinend nicht mehr für den C++ Builder/RAD Studio zu haben. Sonst habe ich kein weiteres Tool gefunden.
-
Du kannst es aber, wenn ich dich richtig verstehe, in einem etwas komplexeren Kontext reproduzieren?
Dann wäre es möglicherweise hilfreich, wenn du mal den für diese Stelle generierten Assembler-Code posten könntest.
-
Argh so, keine Zeit gehabt wieder reinzuschauen.
Auch im komplexen Kontext ist es nur sporadisch / nicht zuverlässig reproduzierbar. Seit dem ich ein paar Umbauarbeiten gemacht habe, lässt es sich bis dato auch nicht mehr reproduzieren (weder Debug noch Release, weder mit noch ohne CodeGuard). Das spricht eigentlich dafür, dass irgendwo an anderer Stelle der Speicher zersägt wird und vermutlich ein Stackframe oder so zerschossen wird. Das funktionierende Minimalbeispiel spricht ja auch dafür.
Da ich keine Ahnung habe woran es liegt und auch toolgestützt nix finden konnte, bleibt mir wohl erstmal nix übrig als das Problem ad akta zu legen
-
So, ich glaub ich hab ihn an den Eiern...
Problem ist anscheinend so ein Konstrukt:
#include <memory> class AImpl; class A { public: A(); A( const A&); A& operator=( const A&); private: std::auto_ptr<AImpl> impl_; }
Okay, ich könnte mich selbst dafür schlagen
aber der Compiler warnte erst im Release-Build nach Aktivierung aller Warnungen (im Debug-Build mit allen Warnungen hielt er es anscheinend nicht für nötig). Die zutreffende war natürlich per Default deaktiviert. Muss mal den Standard durchwühlen, ob das nicht sogar ein Compile-Fehler sein sollte bzw. ob ein Diagnostic erforderlich ist.
Der Code war natürlich an völlig anderer Stelle (wurde allerdings benutzt). Ist auch nicht so verwunderlich, dass der KotGuard nix gefunden hat.
Ich lass den exakten Grund mal gerade noch offen, falls noch jemand drüber grübeln will
(Hm - 5.3.5/5 sagt nur undefined behaviour)
-
Gerade bei sowas wäre eben boost::shared_ptr angebracht, wenn man denn wirklich kopieren will.
-
Ja nee, das hat mit dem Urpsrungsproblem nichts mehr zu tun hier
- es wirkt sich nur darauf aus
- und - boost::shared_ptr wäre genauso auf die Nase gefallen (und außerdem nicht verwendbar in diesem konkreten Beispiel, außer ich schreibe die großen 3 auch alle selbst), bzw. der mistige Compiler hält es nicht für nötig zu warnen.