IDE vergibt unterschiedliche Adressen für dasselbe Objekt



  • Beispiel 1 (Form1.cpp)

    void __fastcall TForm1::FormCreate(TObject *Sender)
    {
       foo (Label1);
       [...]
       String s = "hallo";
       ShowMessage (s);
    }
    

    funktioniert. Wenn ich das ändere z.B. in
    Beispiel 2 (Form1.cpp)

    void __fastcall TForm1::FormCreate(TObject *Sender)
    {
       foo (Label1);
       [...]
       ShowMessage ("hallo");
    }
    

    kriege ich in der foo-Funktion eine AccessViolation.

    Wenn ich mir im Debugger die Adressen für "Label1" und "Form1->Label1" anzeigen lassen, stellt sich heraus, dass im Beispiel 2 beide Werte eine unterschiedliche Adresse kriegen, obwohl Label1 auf Form1 enthalten ist und es eigtl dasselbe sein sollte. Bei der Dereferenzierung von Label1 in der Methode "foo" entsteht dann die Zugriffsverletzung.
    In Beispiel 1 erhalten "Label1" und "Form1->Label1" dieselbe Adresse und es funktioniert wie erwartet.

    Kann sich das jmd erklären? Kann das an irgendeiner Einstellung liegen oder mit Speichergrößen etc.? Vor allem, was hat die untere Zeile mit dem da oben zu tun? Ich verstehe nämlich nur noch Bahnhof 🤕



  • Hi @drummi ,
    nur mal schnell aus dem Bauch; ersteres ist ein String, zweites ein char array. Kanns daran liegen?



  • @drummi: Dann vergleiche mal die Adressen von this und Form1.
    In FormCreate ist die globale Variable Form1 noch nicht gesetzt, (s.a. Application->CreateForm(...) in der <Projekt>.cpp-Datei).
    Dies erklärt aber noch nicht die Zugriffsverletzung, daher zeige mal den Code von foo.



  • @Helmut-Jakoby sollte nicht problematisch sein, soweit ich weiß, aber danke für den Hinweis. Es tritt hier aber tatsächlich immer dann auf, wenn ich irgendeinen Code schreibe, der ein Konstrukt wie "hallo" enthält. Denn immer nur dann haben this und Form1 eine unterschiedliche Adresse. Allerdings wenn ich die Zeile einfach nochmal schreibe, also z.B.:

    void __fastcall TForm1::FormCreate(TObject *Sender)
    {
       foo (Label1);
       [...]
       String s = "hallo";
       ShowMessage (s);
       String t = "welt";
       ShowMessage (t);
    }
    

    dann funktioniert auch Beispiel 1 nicht mehr und ich kriege besagte Zugriffsverletzung.



  • @Th69:
    interessant, this und Form1 haben tatächlich nicht die gleichen Adressen im Beispiel 2. Im Beispiel 1 haben sie dieselbe Adresse. Wenn ich Form1->Label1 schreibe gibt es allerdings keine Zugriffsverletzung, zumindest nicht an der Stelle. Wenn ich nur Label1 schreibe, hat Label1 die selbe Adresse wie this->Label1 und es gibt die Zugriffsverletzung. Müsste es nicht andersrum sein, wenn die globale Variable Form1 noch gar nicht vorhanden ist?

    Ich habe auch den Eindruck, dass in FormCreate die Variablen von Form1 noch nicht gesetzt sind, kann das sein? Gibt es ein Ereignis, was danach kommt, wo alle "Startparameter" schon gesetzt sind, aber bevor das Formular gezeichnet wurde? Weil ich würde gerne Position und Größe aus ner Datei abfragen und anwenden, bevor das Formular sichtbar wird. An welche Stelle setzt man sowas am sinnvollsten hin, wenn es in der OnCreate anscheinend nicht geht?

    zur foo-Funktion: Die hat an sich eigtl keine wirkliche Relevanz für das Beispiel, weil sie bisher auch immer problemlos durchgelaufen ist und seit Jahren nicht mehr verändert wurde. Aber zur Vollständigkeithalber ist das eine Funktion, die auf eine OnGuard-Komponente zugreift, die auf der Form1 liegt und einen Check durchführt. Ist ne vorgefertigte Funktion, auf die ich keinen direkten Zugriff habe und praktisch so aus dem Handbuch übernommen wurde, aber die Funktion an sich wirft ja auch keinen Fehler, sondern der Aufruf der Objektadresse.

    void foo (TOgRegistrationCode*& OgRegGuard)
    {
    
    	try
    	{
    		OgRegGuard->CheckCode(true);  /*HIER wird die Zugriffsverletzung geworfen, 
                                                    beim Zugriff auf die Adresse von OgRegGuard (read of address ...), 
                                                    allerdings landet es nicht im catch, sondern das Programm stürzt ab.*/
    	}
    	catch (...)
    	{
    		throw std::runtime_error ("Prüfung fehlgeschlagen.");
    	}
    }
    


  • Hallo @drummi ,
    etwas verwirrend, aber das klingt irgendwie danach, dass TForm1 noch nicht fertig instanziiert ist. Ich kenne VCL nicht, deshalb kann ich nur raten. Gibt es eine Oberklasse von TForm1? Und wenn, hat die auch eine Methode FormCtreate(...)? Wird diese auch aufgerufen? Wo wird eigentlich Label1 initialisiert? Scheint ja noch nicht erstellt worden sein. Zumindest crasht wohl in foo(...) der Zugriff darauf.



  • @drummi sagte in IDE vergibt unterschiedliche Adressen für dasselbe Objekt:

    interessant, this und Form1 haben tatächlich nicht die gleichen Adressen im Beispiel 2. Im Beispiel 1 haben sie dieselbe Adresse.

    Hab zwar auch keine VCL-Erfahrung, aber ne Idee zum rumprobieren: Schau mal ob sich was ändert, wenn du sicherstellst, dass TForm1 eine polymorphe Klasse ist (falls das nicht schon eh der fall ist):

    class TForm1
    {
        ...
        virtual ~TForm() = default;
        ...
    };
    

    Interessant wäre auch zu sehen, wie TForm1::FormCreate aufgerufen wird. Vielleicht mir irgendeinem komischen Cast oder sowas. Das ist aber möglicherweise irgendwo im VCL-Code vergraben. Wenn this nicht die richtige Adresse hat, kann das eben auch an einem Aufruf á la komischerpointer->FormCreate(...) liegen.

    Dennoch höchst merkwürdig, weil es scheinbar erst dann passiert, wenn du in der Funktion etwas zusätzlich auf dem Stack erstellst (sehr unwahrscheinlich) oder wenn ein neues konstantes String-Literal eingefügt wird.

    Bei letzterem hätte ich sogar tatsächliche ne vage Spekulation: Je nachdem wo und in welcher Sektion in der ausführbaren Datei konstante Strings und VTables landen, könnte ein extra String-Literal tatsächlich die absolute Position des VTables von TForm1 "verschieben". Das ist aber eine ganz normale Linker-Aufgabe sowas richtig zusammenzustoppeln, daher auch nur wilde Spekulation - teste aber dennoch das mit der polymorphen Klasse mal.



  • @drummi: Hast du evtl. aus Versehen OldCreateOrder = true gesetzt? (s.a. OldCreateOrder – er, what?)
    Dann ändert sich die Reihenfolge der Initialisierung.
    Gerade noch mal nachgeschaut: meine alten BCB-Programme greifen auch in FormCreate auf die Controls zu (FormCreate ist eine Ereignismethode, welche man im IDE-Designer zuweist).

    @Finnegan: Jede (von der IDE) erstellte Form-Klasse erbt von TForm und hat automatisch einen virtuellen Destruktor.

    Leider scheint (seit mindestens gestern) die gesamte Produkt-Dokumentation für RADStudio 12 (Athens) nicht mehr zu funktionieren (ich erhalte dort nur entweder leere Seiten oder aber eine Fehlermeldung bzgl. Datenbank-Zugriff)...



  • Schonmal vielen Dank für eure ganzen Ideen! 🙂

    @Helmut-Jakoby : genau, es gibt die TForm als Oberklasse, die von diversen (TCustomForm bis zu TObject) abgeleitet ist. Wird aber beim Anlegen von der IDE automatisch "kreiert". Ist eine gute Frage wo Label1 tatsächlich initialisiert wird, da das der Designer beim Anlegen ebenfalls automatisch schreibt. In der Header-Datei taucht die Deklaration des Variablennamens als Pointer im published Bereich auf und das müsste dann, soweit ich das verstehe, durch den Konstruktor letztlich initialisiert werden.

    @Finnegan: wie Th69 schon sagte, hat der automatisch nen virtuellen Konstruktor. Habe spaßeshalber trotzdem mal ausprobiert den nochmal manuell zu überschreiben, aber das ändert nichts an dem Verhalten. In der main{}-Funktion wird durch Application->CreateForm(__classid(TForm1), &Form1) der Konstruktor aufgerufen und erst anschließend das OnCreate-Ereignis aufgerufen. Deswegen hätte ich auch gedacht, dass dort alle Variablen bereits initialisiert wurden. Laut folgendem Beitrag passiert das auch (bezieht sich zwar auf Delphi, aber sollte hier genauso funktionieren):

    Dadurch das aber bei CreateForm erst der Speicher angefordert und der Variablen zugewiesen wird, und dann erst der Constructor aufgerufen wird kann man innerhalb des Constructors bereits auf die Variable zugreifen.
    Quelle: https://www.delphipraxis.net/103915-geloest-application-createform.html

    Woanders liest man hingegen:

    This article speaks about the dangers of accessing the form instance in its own OnCreate event handler. One way to avoid this is to refrain to doing any initialization in main form's OnCreate handler. Wait until main form is truly ready. For this send a message to your self.
    Quelle: https://stackoverflow.com/questions/29786757/application-createform-forms-visibility-state

    Leider ist der Artikel nicht mehr verfügbar. Aber es scheint, dass man die OnCreate Funktion eigtl gar nicht nutzen sollte und ich das bisher immer falsch gemacht habe ... 🧐

    @Th69 : Meinst du, es wäre sinnvoll, in der main-Funktion zwischen Application->CreateForm(...) und Application->Run() ne Init()-Funktion einzubauen, die man durch ne externe Unit inkludiert? Und dass ich da den ganzen Kram aus dem OnCreate erst reinschreibe? An der Stelle müsste dann ja spätestens alles bekannt und korrekt initialisiert sein. Oder ist es klüger, so wie oben vorgeschlagen am Ende von OnCreate eine PostMessage zu senden und darauf zu reagieren?

    An OldCreateOrder hatte ich auch schon gedacht, aber nö, die steht auf false. Hab's auch mal auf true und wieder zurückgesetzt und alles neu compiliert, aber macht keinen Unterschied. Habe heute Morgen auch mal mit den Optionen 'this' zuerst und VTable nach vorn rumgetestet, aber gleiches Ergebnis. Also mittlerweile scheint es mir, dass @Finnegan mit seiner Spekulation Recht haben könnte und da beim Compilen/Linken Murks passiert (was ja bei Emba nicht selten ist...) oder dass das generell einer der Gründe ist, warum man die OnCreate nicht benutzen soll. Warum es sie dann gibt ist mir dann allerdings schleierhaft. Und das sind eh alles schon wieder Probleme, mit denen ich mich überhaupt nicht rumschlagen wollte und ziemlich frustrierend sind 🙈 🤯

    Die Doku-Seite ist derzeit nicht verfügbar, weil die nen Hardware-Ausfall im Rechenzentrum haben (ich benutze allerdings auch noch Tokyo): https://blogs.embarcadero.com/we-are-experiencing-a-hardware-outage/



  • Setz' doch mal ein Testprojekt auf und schau, ob das dort funktioniert.
    Ich denke, irgendwas ist an deinem jetzigen Projekt falsch eingestellt.

    Ansonsten kannst du eines der anderen Ereignisse (OnShow, OnActivate) verwenden: Creating and Closing Forms - evtl. mit einem bool-Flag für nur einmaligen Aufruf deiner Funktionen.

    PS: Danke für den Link bzgl. Hardware-Ausfall bei Embarcadero.



  • @Th69 werde ich mal probieren, danke. Beim Stöbern in der BCB-Hilfe habe ich noch einen Hinweis entdeckt, der besagt, dass man die FormCreate nur in Delphi verwenden solle, aber nicht in C++, da da irgendwelche Verbindungen zum Konstruktor nicht funktionieren würden (die genaue Formulierung hab ich nicht mehr im Kopf) ... vllt auch ne Erklärung. Allerdings machen die ja selbst Beispiele in FormCreate in C++, naja...



  • Ich habe gelernt, bei C++ Konstruktor und Destruktor anstelle von OnCreate und OnDestroy zu verwenden. Die Embarcadero-Hilfeseite ist leider immer noch offline, bei Stackoverflow findet man diesbezüglich aber Hinweise anerkannter Experten.

    C++ Builder Lifetime of Static Class Property/Variable of a dynamicly created form?
    "That being said, you should not be using the TForm::OnDestroy (or OnCreate) event in C++ to begin with. They are Delphi idioms that can cause undefined behavior in C++, as Delphi and C++ use different object creation/cleanup semantics. The OnDestroy event can occur after your C++ destructor (and the OnCreate event can occur before your C++ constructor). So, in C++, always use your TForm's actual destructor (and constructor) instead."

    Deleting dynamically created component?
    "On a side note: you should NOT use the Form's OnCreate and OnDestroy events in C++Builder. They are a Delphi idiom that can lead to undefined behavior in C++ if you are not careful. OnCreate can fire before your Form's constructor, and OnDestroy can fire after your Form's destructor. As such, you should use the Form's actual constructor and destructor instead."


Anmelden zum Antworten