Referenz auf noch nicht initialisiertes Objekt binden



  • @Finnegan sagte in Referenz auf noch nicht initialisiertes Objekt binden:

    alignas(Object) std::byte object_storage[sizeof(Object)];
    Object& object = reinterpret_cast<Object>(object_storage);

    Ich sehe hier auf keine Bedenken. Ich gestehe aber, dass ich hier an meine C Projekte denke:

    #pragma pack(1)
    struct Object
    {
        int data;
    };
    #pragma pack()
    
    static const size_t ObjSize = sizeof(Object);
    uint8_t object_storage[ObjSize];
    Object* object = (Object*)object_storage;
    

    In meinem Fall ist object_storage immer serialisierte Daten und Object eine mögliche Interpretation.


  • Mod

    @Finnegan Das ist schon allein deshalb nicht problematisch, weil Referenzen ja auch auf bspw. globale/externe Variablen verweisen koennen, die aufgrund der Initialisierungsreihenfolge erst nach der Referenz initialisiert werden.

    Siehe auch https://eel.is/c++draft/dcl.ref#6, wo dein Beispiel auch quasi als wohldefiniert impliziert wird.



  • @john-0 sagte in Referenz auf noch nicht initialisiertes Objekt binden:

    Du initialisierst hier nicht, Du machst eine Konstruktion eines Objektes (placement new) in einem Array, was das passende Alignment hat. Daran ist nichts auszusetzen.

    Oh, ist "Konstruktion" nicht auch eine Form von "Initialisierung", oder braucht hier mein Begriffsverständnis ein Update?

    @Quiche-Lorraine sagte in Referenz auf noch nicht initialisiertes Objekt binden:

    In meinem Fall ist object_storage immer serialisierte Daten und Object eine mögliche Interpretation.

    Ja, in C hätte ich mit so einem Konstrukt auch keinerlei Bedenken gehabt. Mir gings hier vor allem darum, dass der Compiler nicht z.B. denkt, dass er die Referenz so "optimieren" kann, dass er sie gar nicht erst initialisiert, weil die ja ohnehin auf kein Objekt verweist. "UB kann nicht auftreten"-Optimierungen können ätzend zu debuggen sein, wenn man tatsächlich irgendwo UB hat.

    @Columbo sagte in Referenz auf noch nicht initialisiertes Objekt binden:

    Siehe auch https://eel.is/c++draft/dcl.ref#6, wo dein Beispiel auch quasi als wohldefiniert impliziert wird.

    Ja danke. Da hab ich mich gestern schwer getan das zu finden. "The object designated by such a glvalue can be outside its lifetime" in Note 2 ist nach meinem Verständnis eine Aussage, die das explizit erlaubt.

    Das erinnert mich auch daran, dass ich auf den Sonderfall des "null pointer" achten muss. Der betreffende Code ist in einem x86-Baremetal-Hobbyprojekt, wo mein Linker-Skript tatsächlich Daten so platzieren kann, dass sie im geladenen Programm an ds:0x0 liegen. Da brauch ich noch ein Padding, wenn ich keine bösen Ohrfeigen vom Compiler bekommen will - const char* hello = "Hello World"; und (hello == nullptr) ist bestimmt nicht gesund. 😁


  • Gesperrt

    @Finnegan sagte in Referenz auf noch nicht initialisiertes Objekt binden:

    Mir gings hier vor allem darum, dass der Compiler nicht z.B. denkt, dass er die Referenz so "optimieren" kann, dass er sie gar nicht erst initialisiert, weil die ja ohnehin auf kein Objekt verweist.

    Auch Compileroptimierungen nehmen eine Variable oder Referenz nicht einfach so raus, solange diese noch im Gültigkeitsbereich ist und gelesen wird.



  • @noLust Compiler Optimierungen dürfen und machen alles, so lange es das vom Nutzer beobachtbare Verhalten nicht ändert. Es gibt da lustige Fälle, in denen zum Beispiel ein überschreiben des Speichers weg optimiert wird, wenn der Speicherbereich hinterher nicht mehr benutzt wird, was man aber unter Umständen explizit haben möchte, z.B. in kryptographischen Anwendungen.



  • @Finnegan sagte in Referenz auf noch nicht initialisiertes Objekt binden:

    Mir gings hier vor allem darum, dass der Compiler nicht z.B. denkt, dass er die Referenz so "optimieren" kann, dass er sie gar nicht erst initialisiert, weil die ja ohnehin auf kein Objekt verweist.

    Ich verstehe diesen Satz nicht.

    Kein Objekt? Du legst doch die globale Variable object_storage an. Und diese wird vom Compiler zero-initialized.

    https://en.cppreference.com/w/cpp/language/zero_initialization

    Danach legst du eine Referenz namens object an, welche auf object_storage verweist. Der cast ist ein wenig verwirrend, aber ok.

    Und danach machst du ein placement new. Du könntest aber hier genauso object.data = 42; schreiben.

    "UB kann nicht auftreten"-Optimierungen können ätzend zu debuggen sein, wenn man tatsächlich irgendwo UB hat.

    Kenne ich. Ich hatte vor kurzem dank "The Old New Thing" ein WinAPI Rätsel zum Thema Subclassing gelöst, wo nur Dr. Memory motzte.



  • @Finnegan sagte in Referenz auf noch nicht initialisiertes Objekt binden:

    Oh, ist "Konstruktion" nicht auch eine Form von "Initialisierung", oder braucht hier mein Begriffsverständnis ein Update?

    Dein Begriffsverständnis braucht ein Update. Ein C++ Objekt durchläuft zwei Phasen beim Erzeugen:

    • Die Konstruktion: Dabei wird das Objekt erzeugt, und man darf danach mit den Mittel von C++ darauf zu greifen. (POD-Typen brauchen keine Konstruktion.)
    • Die Initialisierung: Dabei wird das Objekt in einen programmlogischen wohldefinierten Zustand gebracht.

    Die Designphilosophie RAII versucht gerade wann immer möglich diese beiden Phasen zusammen zu legen. Seit C++11 ist es nun noch sehr selten notwendig vom RAII Gedanken abzuweichen und z.B. separate init Funktionen zu schreiben. Früher gab es das häufiger.

    In Deinem Fall ist das nun so, dass nach dem placement new über die Referenz auf das Objekt zugegriffen werden darf, da es konstruiert wurde. Dieser Aspekt ist hier wichtig. Bei nonPOD Objekten darf der Compiler nämlich noch Compilermagie betreiben und Weiteres als die offensichtlichen Member per Konstruktor initialisieren. Bei einem POD-Typen würde ein simpler Cast ausreichen, damit darauf zugegriffen werden kann.



  • @Quiche-Lorraine sagte in Referenz auf noch nicht initialisiertes Objekt binden:

    @Finnegan sagte in Referenz auf noch nicht initialisiertes Objekt binden:

    Mir gings hier vor allem darum, dass der Compiler nicht z.B. denkt, dass er die Referenz so "optimieren" kann, dass er sie gar nicht erst initialisiert, weil die ja ohnehin auf kein Objekt verweist.

    Ich verstehe diesen Satz nicht.

    Kein Objekt? Du legst doch die globale Variable object_storage an. Und diese wird vom Compiler zero-initialized.

    Sorry, der Satz ist auch etwas lang geworden. Meine Befürchtung war: Compiler sieht, da ist kein Object, sondern nur ein byte[sizeof(Object)]. Wenn das UB wäre, hätte die z.B. die Initialisierung der Referenz kommentarlos rausfliegen können.



  • @john-0 sagte in Referenz auf noch nicht initialisiertes Objekt binden:

    @Finnegan sagte in Referenz auf noch nicht initialisiertes Objekt binden:

    Oh, ist "Konstruktion" nicht auch eine Form von "Initialisierung", oder braucht hier mein Begriffsverständnis ein Update?

    Dein Begriffsverständnis braucht ein Update. Ein C++ Objekt durchläuft zwei Phasen beim Erzeugen:

    • Die Konstruktion: Dabei wird das Objekt erzeugt, und man darf danach mit den Mittel von C++ darauf zu greifen. (POD-Typen brauchen keine Konstruktion.)
    • Die Initialisierung: Dabei wird das Objekt in einen programmlogischen wohldefinierten Zustand gebracht.

    Die Designphilosophie RAII versucht gerade wann immer möglich diese beiden Phasen zusammen zu legen. Seit C++11 ist es nun noch sehr selten notwendig vom RAII Gedanken abzuweichen und z.B. separate init Funktionen zu schreiben. Früher gab es das häufiger.

    Okay. So wie ich das verstehe:

    std::ofstream out;
    out.write("ABC", 3);
    

    Hier würdest du out als konstruiert, aber nicht initialisiert bezeichnen?

    So wie ich die Begriffe bisher verwendet habe ist out beides. Interne Datenstrukturen wie File Handle wurden im Konstruktor mit irgendwelchen Werten initialisiert, verweisen aber auf keine Datei. Ich hätte hier eher von fehlerhafter Initialisierung gesprochen.

    Aber ich denke ich verstehe, worauf du hinaus willst: Ein int value; ist konstruiert, aber nicht initialisiert, während ein int value = 0; initialisiert ist. Nach meinem bisherigen Verständnis fällt es mir bei Objekten mit Konstruktor aber schwer, Konstruktion von Initialisierung überhaupt zu trennen, da auch ein Object object; via Default-Konstruktor initialisiert wird (Default Initialization). Selbst wenn es nur ein explizit leerer oder der compiler-generierte Default-Konstruktor ist.

    Daher bin ich bei dem Argument mit dem RAII auch noch skeptisch. Wenn ich mir den verlinkten cppreference-Artikel ansehe, dann wird da der Begriff schon etwas anders verwendet. Auch der C++-Standard nennt das so wie ich das sehe auch "initialization". Der erwähnt zwar auch Fälle wo keine Initialisierung stattfindet, aber die sind lediglich für primitive Datentypen oder Referenzen/Pointer und sowas. Sobald ein anwendbarer Konstruktor da ist, findet laut Standard "Initialisierung" statt, indem dieser aufgerufen wird.

    Ich weiß aber, was du meinst - für dich ist "Initialisierung" von der Programmlogik abhängig (write ohne open ist ein Bug). Da macht das dann mit dem RAII auch Sinn. Ich glaube aber dennoch, das ist zumindest aus Perspektive des Standards nicht ganz korrekt - auch wenn ich deine Definition für den praktischen Einsatz durchaus als sinnvoll erachte.


  • Mod

    @Finnegan Keine Ahnung was dieser Kasper da schon wieder faselt. Ich glaube er hat den Begriff "Allokation" mit "Konstruktion" verwechselt oder so. Um sich wichtig zu machen. Keine Ahnung.

    Es gibt nur eine solche Phase im Bezug auf die Lebenszeit eines Objekts, und das ist die Initialisierung (die im Standardjargon weitgehend synonym zu Konstruktion ist). Dass Initialisierung eben manchmal ausbleibt, also ein Objekt vacuous initialized ist, ist zwar terminologisch ungluecklich, aber hat nichts mit 'construction' zu tun (was ein Begriff ist, der sich auf die Phase der Ausfuehrung aller Konstruktoren, member initializer etc. eines Klassenobjekts bezieht).

    Ein int value; ist konstruiert, aber nicht initialisiert,

    Formell: ein int value ist default-initialized, was also keine eigentliche Initialisierung ist (weil nix passiert), was ergo vacuous initialization ist. Was aber insbesondere wichtig ist, ist die Tatsache, dass das Objekt am leben ist, und dass ein Zugriff wohldefiniert (auch wenn fehlerhaft) ist.

    Es ist aber sowohl umgangssprachlich als auch normativ korrekt zu sagen: i ist nicht initialisiert.

    Da macht das dann mit dem RAII auch Sinn. Ich glaube aber dennoch, das ist zumindest aus Perspektive des Standards nicht ganz korrekt - auch wenn ich deine Definition für den praktischen Einsatz durchaus als sinnvoll erachte.

    Der Standard redet einerseits von Initialization, und andererseits von bestimmten definierten Begriffen wie "vacuous initialization" und "default-initialization" etc. der Begriff "Initialisierung" an sich ueberlappt sich durchaus mit dem gemeinen Gebrauch.

    Die Designphilosophie RAII versucht gerade wann immer möglich diese beiden Phasen zusammen zu legen.

    🤣 Bist jetzt auch noch Philosoph, ey?