static_cast von void* auf Derived und FirstBase


  • Mod

    Also das letzte mal als ich mich in die Untiefen des Standards (C++98) vorgekämpft habe, hatte ich entschieden den Eindruck dass PODs sich quasi selbst erzeugen, indem man sie einfach in den Speicher schreibt.

    Nein, du hast wohl den Paragraphen in [basic.life] gesehen, der sagt, dass Objekte mit vacuous initialization nach Allokation ihres Speichers zu leben beginnen. Das haben schon einige falsch interpretiert. Es bezieht sich auf deklarierte Objekte; denn, wie gesagt, malloc allein erzeugt kein Objekt (und dieses hätte auch keinen Initializer).

    Ich müsste mir die entsprechenden Stellen nochmal raussuchen, aber was ich mich erinnere ist es im Standard ziemlich eindeutig erlaubt eingebaute Typen sowie PODs zu erzeugen indem man sie Byteweise (per char/unsigned char) schreibt.

    Natürlich. Mittels memcpy . Das hat mit Iteration mittels char* wenig zu tun.

    Natürlich wäre es besser es ganz wegzuoptimieren, aber der Unterschied zwischen dem was mit diesen neuen Regeln möglich wäre, und dem was so schon geht, ist glaube ich nicht so besonders gross.

    Doch, mir scheint, da ist eine signifikante Lücke. Du musst in jedem Schleifendurchlauf einen load und eine branch extra ausführen. Die Branch wird natürlich quasi nie mispredicted, aber

    hustbaer schrieb:

    Arcoth schrieb:

    Aber selbst wenn wir das mal übersehen, und davon ausgehen dass jemand dort ein placement new reinhaut, sticht immer noch

    RefCountedWString* const rcws = reinterpret_cast<RefCountedWString*>(reinterpret_cast<char*>(s) - (sizeof(size_t) * 2));
    

    ins Auge. Das wird definitiv nicht mehr wohldefiniert sein.

    Tja, doof. Gibt nämlich wie gesagt definitiv Code der genau das macht.

    Hoffen wir mal, dass er weniger in sicherheitskritischen Programmen vorkommt. 😉



  • n3337, [basic.life] schrieb:

    The lifetime of an object of type T begins when:
    — storage with the proper alignment and size for type T is obtained, and
    — if the object has non-trivial initialization, its initialization is complete.

    Da steht "storage is obtained". Wenn ich das falsch interpretiert habe, dann würde ich a) anraten den entsprechenden Absatz neu zu formulieren und b) würde mich interessieren aus welchen Stellen des Standards das klar hervorgeht.

    Arcoth schrieb:

    Ich müsste mir die entsprechenden Stellen nochmal raussuchen, aber was ich mich erinnere ist es im Standard ziemlich eindeutig erlaubt eingebaute Typen sowie PODs zu erzeugen indem man sie Byteweise (per char/unsigned char) schreibt.

    Natürlich. Mittels memcpy . Das hat mit Iteration mittels char* wenig zu tun.

    Also ich kann im Standard quasi nix zum Thema memcpy finden (hab hier nur n3337 zur Verfügung, aber ich hab ja glaub ich auch schon geschrieben dass ich diesbezüglich nur ältere Versionen kenne).
    Wird 1x kurz in [basic.types] erwähnt.

    n3337, [basic.types] schrieb:

    For any object (other than a base-class subobject) of trivially copyable type T, whether or not the object
    holds a valid value of type T, the underlying bytes (1.7) making up the object can be copied into an array
    of char or unsigned char. [40] If the content of the array of char or unsigned char is copied back into the
    object, the object shall subsequently hold its original value.

    Und dort steht in Fussnote [40] explicit "for example". Die Stelle auf die du dich glaube ich schon bezogen hast. Für mich ist die Sache aber ziemlich klar, wenn da steht "bytes kopieren, z.b. mit memcpy oder memmove", dann heisst das ganz sicher nicht dass nur memcpy oder memmove erlaubt sind.

    Wenn du anderer Meinung bist, dann bitte begründe das.

    Also nochmal, was für mich ganz klar ist, ist das folgendes OK ist:

    struct POD
    {
        int x;
    };
    
    void PodConsumer(POD*);
    
    void Foo()
    {
        POD* pod = malloc(sizeof(POD));
        if (!pod)
            exit(1);
        pod->x = 42;
        PodConsumer(pod);
    }
    

    Wenn das nicht gegeben wäre, dann wäre die C-Kompatibilität von C++ gleich 0. Und tonnenweise C++ Code falsch. Da helfen auch Smileys und "hoffentlich nicht sicherheitskritisch" nix.

    Ob hierbei jemals irgendwie ein "POD" Objekt entsteht, oder nur ein "int" Objekt, ist letztendlich egal. Wichtig ist dass es legal ist und das definierte Verhalten das ist was sich jeder mir bekannte C Programmierer erwartet wenn er diesen Code liest.

    ps:
    Falls du dich auf [intro.object] "An object is created by a definition (3.1), by a new-expression (5.3.4)
    or by the implementation (12.2) when needed." beziehst...
    Diese Liste kann mMn. nur unvollständig sein. Dann würde nämlich auch malloc + memcpy/memset nicht ausreichen.


  • Mod

    hustbaer schrieb:

    n3337, [basic.life] schrieb:

    The lifetime of an object of type T begins when:
    — storage with the proper alignment and size for type T is obtained, and
    — if the object has non-trivial initialization, its initialization is complete.

    Da steht "storage is obtained". Wenn ich das falsch interpretiert habe, dann würde ich a) anraten den entsprechenden Absatz neu zu formulieren und b) würde mich interessieren aus welchen Stellen des Standards das klar hervorgeht.

    Das geht klar aus dem wording hervor. Ich habe das auch schon auf Stackoverflow angesprochen. Allerdings bist du bei weitem nicht der erste, der diesen Paragraphen misinterpretiert; vielleicht sollte man mal eine Notiz einfuegen.

    Arcoth schrieb:

    Ich müsste mir die entsprechenden Stellen nochmal raussuchen, aber was ich mich erinnere ist es im Standard ziemlich eindeutig erlaubt eingebaute Typen sowie PODs zu erzeugen indem man sie Byteweise (per char/unsigned char) schreibt.

    Natürlich. Mittels memcpy . Das hat mit Iteration mittels char* wenig zu tun.

    Also ich kann im Standard quasi nix zum Thema memcpy finden (hab hier nur n3337 zur Verfügung, aber ich hab ja glaub ich auch schon geschrieben dass ich diesbezüglich nur ältere Versionen kenne).
    Wird 1x kurz in [basic.types] erwähnt.

    n3337, [basic.types] schrieb:

    For any object (other than a base-class subobject) of trivially copyable type T, whether or not the object
    holds a valid value of type T, the underlying bytes (1.7) making up the object can be copied into an array
    of char or unsigned char. [40] If the content of the array of char or unsigned char is copied back into the
    object, the object shall subsequently hold its original value.

    Und dort steht in Fussnote [40] explicit "for example". Die Stelle auf die du dich glaube ich schon bezogen hast. Für mich ist die Sache aber ziemlich klar, wenn da steht "bytes kopieren, z.b. mit memcpy oder memmove", dann heisst das ganz sicher nicht dass nur memcpy oder memmove erlaubt sind.

    Wenn du anderer Meinung bist, dann bitte begründe das.

    Ich habe mit dem project editor gesprochen. Dem Autor von P0137 und dutzenden anderer Paper, einem der wichtigsten CWG Mitglieder. Der hat unmissverstaendlich erklaert, dass es noch nie wohldefiniert war, mittels char* ueber etwas zu iterieren, was kein Array ist. Und dass die Fussnote, auf die ich mich in meinem letzten Beitrag auch schon bezogen habe, nicht normativ ist, und irrefuehrend, weil sie genau deine Schlussfolgerung suggeriert.

    Also nochmal, was für mich ganz klar ist, ist das folgendes OK ist:

    struct POD
    {
        int x;
    };
    
    void PodConsumer(POD*);
    
    void Foo()
    {
        POD* pod = malloc(sizeof(POD));
        if (!pod)
            exit(1);
        pod->x = 42;
        PodConsumer(pod);
    }
    

    Wenn das nicht gegeben wäre, dann wäre die C-Kompatibilität von C++ gleich 0. Und tonnenweise C++ Code falsch. Da helfen auch Smileys und "hoffentlich nicht sicherheitskritisch" nix.

    Du hast Recht. Millionen LOC waehren dahin. Daher werden diese Regeln sehr wahrscheinlich auch angepasst. 👍

    Ob hierbei jemals irgendwie ein "POD" Objekt entsteht, oder nur ein "int" Objekt, ist letztendlich egal. Wichtig ist dass es legal ist und das definierte Verhalten das ist was sich jeder mir bekannte C Programmierer erwartet wenn er diesen Code liest.

    Warum muss C++ eine so strikte Obermenge von C sein?!

    Falls du dich auf [intro.object] "An object is created by a definition (3.1), by a new-expression (5.3.4)
    or by the implementation (12.2) when needed." beziehst...
    Diese Liste kann mMn. nur unvollständig sein. Dann würde nämlich auch malloc + memcpy/memset nicht ausreichen.

    Liste mal auf, was da deines Erachtens nach noch reingehoert.

    PS: Hol dir mal N4640. Was ist daran so schwer, ein PDF zu downloaden?


  • Mod

    Das wird immer wirrer. Ich bekomme den Eindruck, dass hier etwas in den Standard(entwurf) eingefügt wurde und man überlegt sich erst hinterher, was das eigentlich bedeuten soll. Hier haben wir etwas, dass bestehenden (historisch erwartbar korrekten) Code als undefiniert erklärt, ohne dass es Möglichkeiten gäbe, derartige Stellen sicher automatisiert aufzufinden (wenn der Compiler das könnte, wäre launder ja überflüssig). Gerade in einem solchen Fall erwarte ich doch eine Demonstration, dass es diese Änderung tatsächlich Wert ist (Erfahrung mit einer entsprechenden Implementation, Benchmarks an real-world Code; Belege, dass der entsprechende Performancegewinn nur so erreicht werden kann).
    Andernfalls fürchte ich, dass das zum export von C++17 wird: entweder implementiert kein Compiler die Regln, oder bestehende Projekte werden einfach nicht portiert.

    Die Behauptung, man könnte (bisher) nicht per char* über beliebige Objekte iterieren ist doch sehr gewagt: damit würden [intro.memory], [basic.types](object repräsentation) und [basic.lval] (char aliasing) gegenstandslos.


  • Mod

    Hast du den ersten Paragraphen meiner Antwort zu deinem ersten Beitrag gelesen? Es ist erwartet, dass diese Regeln zu strikt sind. C++ bewegt sich gerade aus dem Moor vager Regelsaetze heraus, und das beinhaltet nunmal, dass ploetzlich Regeln klar dargelegt werden, die nie befolgt wurden.

    Und du musst mir schon erklaeren, inwiefern [intro.memory] gegenstandslos wird. Oder Objektrepraesentierung. Nur weil ich nicht mittels eines char s iterieren kann, heisst das nicht, dass ich nicht das Objekt mittels memcpy in ein char Array kopieren und dieses inspizieren kann.


  • Mod

    Arcoth schrieb:

    Hast du den ersten Paragraphen meiner Antwort zu deinem ersten Beitrag gelesen?

    Ja, hatte es für einen Scherz gehalten.

    Arcoth schrieb:

    Es ist erwartet, dass diese Regeln zu strikt sind.

    Wäre das Wikipedia, würde ich diesen Satz mit einem [Who?] markieren. Kann ja eigentlich nicht der Author sein.

    Arcoth schrieb:

    C++ bewegt sich gerade aus dem Moor vager Regelsaetze heraus, und das beinhaltet nunmal, dass ploetzlich Regeln klar dargelegt werden, die nie befolgt wurden.

    Niemand hat etwas gegen klare Sprache, aber die ist kein Selbstzweck und der Standard (hoffentlich) keine Spielwiese. Dass da schon Regeln waren, die regelmäßig gebrochen wurden, sollte demonstriert und nicht bloß behauptet werden. Und da genügt es nicht, Regeln zu finden, die man nur mit viel Mühe so auslegen kann. Auch gelebte Praxis hat einen gewissen normativen Wert.

    Arcoth schrieb:

    Und du musst mir schon erklaeren, inwiefern [intro.memory] gegenstandslos wird. Oder Objektrepraesentierung. Nur weil ich nicht mittels eines char s iterieren kann, heisst das nicht, dass ich nicht das Objekt mittels memcpy in ein char Array kopieren und dieses inspizieren kann.

    Weil es das Pferd von hinten aufzäumt. Zu Aliasing sagst du ja schon nichts. Und wenn memcpy und memmove die einzigen Funktionen sind, die das erlauben, dann sind diese Abschnitte nicht Teil des Sprachkerns, sondern bloß Magie der Standardbibliothek und systematisch an völlig falscher Stelle befindlich. Wäre auch intressant herauszufinden, ob Kernighan auch der Ansicht ist, dass memcpy etwas Magisches macht, dass nicht auch auf anderem Wege erreicht werden kann.


  • Mod

    Merkwürdig, es gibt ein core issue zu diesem Thema:

    <a href= schrieb:

    CWG 1314">According to 3.9 [basic.types] paragraph 4,

    The object representation of an object of type T is the sequence of N unsigned char objects taken up by the object of type T , where N equals sizeof(T) .

    and 1.8 [intro.object] paragraph 5,

    An object of trivially copyable or standard-layout type (3.9 [basic.types]) shall occupy contiguous bytes of storage.

    Do these passages make pointer arithmetic (5.7 [expr.add] paragraph 5) within a standard-layout object well-defined (e.g., for writing one's own version of memcpy?

    Rationale (August, 2011):
    The current wording is sufficiently clear that this usage is permitted.

    Interessant. Ich sehe nämlich nicht, inwiefern das wording diese Nutzung erlaubt, aber ich denke, die Intention ist schon, das zu erlauben. Ich werde den project editor wohl heute Abend danach fragen.

    Niemand hat etwas gegen klare Sprache, aber die ist kein Selbstzweck und der Standard (hoffentlich) keine Spielwiese. Dass da schon Regeln waren, die regelmäßig gebrochen wurden, sollte demonstriert und nicht bloß behauptet werden.

    Dass Zeigerarithmetik nur über Arrays funktioniert, dürfte dir bekannt sein. [expr.add]/5. Was gibt es da zu diskutieren? Aber siehe oben, das scheint wohl irgendwie unvollständig zu sein.

    Und da genügt es nicht, Regeln zu finden, die man nur mit viel Mühe so auslegen kann. Auch gelebte Praxis hat einen gewissen normativen Wert.

    Große Mengen von Code die eine gewisse Regel verletzen, machen es unumgänglich, diese Regel anzupassen. Die Absicht des Komitees war es auch nicht, alle diese Regeln wider aller Umstände umzusetzen. Was ich auch bereits erwähnt habe. Das wird alles abgeschliffen werden.

    Und wenn memcpy und memmove die einzigen Funktionen sind, die das erlauben, dann sind diese Abschnitte nicht Teil des Sprachkerns, sondern bloß Magie der Standardbibliothek und systematisch an völlig falscher Stelle befindlich.

    Das selbe könnte über initializer_list gesagt werden. Nein, ich denke, das würde schon so passen. Ich sehe auch überhaupt keinen Grund für ein eigenes memcpy .



  • @Arcoth
    Das ganze Konzept von "Objekten" das der C++ Standard versucht aufzuziehen ist mMn. problematisch. Also zumindest für PODs. Eben weil da gewisse Dinge, wenn man C++ halbwegs C-kompatibel halten will, einfach nicht hinhauen.

    Ich glaube es wäre vermutlich besser das Konzept "Objekte" für POD-structs mehr oder weniger fallen zu lassen.

    Die Regeln könnten vielleicht ca. so aussehen...

    - Schreiben von eingebauten Typen in beliebige Speicherbereiche mit passender Grösse und Alignment ist immer OK, und erzeugt
      dort ein entsprechendes Objekt (falls es nicht bereits existiert).
    - Lesen von eingebauten Typen ist immer OK wenn ...
      - an der Adresse bereits ein Objekt passenden Typs existiert ODER
      - kein Byte im betroffenen Speicherbereich zu einem existierenden Objekt gehört (ausgenommen char bzw. unsigned char Objekte)
    - Member-Zugriff über POD-structs ist lediglich als Adressberechnung + Zugriff auf eingebaute Typen zu sehen. Ob dabei ein
      Objekt des POD-struct Typs existiert ist völlig egal.
      Der "Adressberechnung" Teil davon ist immer legal. Ob der Zugriff dann legal ist entscheidet sich sobald wirklich ein
      eingebauter Typ gelesen oder geschrieben wird. Dabei gelten die selben Regeln wie wenn die Adresse sonstwo herkommen würde.
    - Wenn POD-structs als ganzes gelesen/geschrieben werden (z.B. [c]*pod = *pod2[/c]), ist das was die Regeln angeht so zu betrachten
      wie wenn alle Member des POD-structs (rekursiv, bis wir bei eingebauten Typen angelangt sind) einzeln geschrieben/gelesen würden.
    

    Die in [intro.object] aufgeführten Möglichkeiten Objekte zu erzeugen blieben natürlich erhalten.

    Das würde z.B. auch bedeuten dass man drei floats hintereinander in den Speicher schreiben kann, und sie dann als Vector3<float> auslesen darf (passende Definition von Vector3<float> vorausgesetzt). (Bin mir nicht sicher ob das aktuell legal ist, aber es ist "gelebte Praxis").

    Wenn man möchte kann man dann definieren dass das Lesen/Schreiben eines POD-structs, sofern nach den obigen Regeln legal, dort ein POD-struct Objekt erzeugt.

    Warum muss C++ eine so strikte Obermenge von C sein?!

    Weil es bereits so massiv viel C++ Code gibt der sich darauf verlässt. Den kannst du nicht einfach umschreiben.

    Warum muss die POSIX API stellenweise auf katastrophale Weise mistig sein, dadurch dass sie halt ist wie sie ist, und nicht anders? Selber Grund. Muss man nicht mögen. Aber man sollte mMn. verstehen und respektieren was die Konsequenzen eines "breaking change" wären.

    Wenn garantiert wäre dass jede (oder wenigstens so-gut-wie jede) Regelverletzung vom Compiler diagnostiziert werden kann, dann wäre das weniger ein Thema. Geht aber bei der Art von Regeln die wir hier diskutieren nicht.

    Arcoth schrieb:

    Ich habe mit dem project editor gesprochen. Dem Autor von P0137 und dutzenden anderer Paper, einem der wichtigsten CWG Mitglieder. Der hat unmissverstaendlich erklaert, dass es noch nie wohldefiniert war, mittels char* ueber etwas zu iterieren, was kein Array ist. Und dass die Fussnote, auf die ich mich in meinem letzten Beitrag auch schon bezogen habe, nicht normativ ist, und irrefuehrend, weil sie genau deine Schlussfolgerung suggeriert.

    Hmja. Gut, dann verstehe ich wie du zu der Auffassung kommst. Ich halte sie aber trotzdem für falsch. Also falsch in dem Sinn, dass ich der Meinung bin dass es falsch wäre den Standard so anzupassen dass diese Auslegung zur einizg möglichen wird. Weil eben gelebte Praxis und so. Wenn dann würde ich den Standard "fixen" indem man es explizit erlaubt.


  • Mod

    Arcoth schrieb:

    Niemand hat etwas gegen klare Sprache, aber die ist kein Selbstzweck und der Standard (hoffentlich) keine Spielwiese. Dass da schon Regeln waren, die regelmäßig gebrochen wurden, sollte demonstriert und nicht bloß behauptet werden.

    Dass Zeigerarithmetik nur über Arrays funktioniert, dürfte dir bekannt sein. [expr.add]/5. Was gibt es da zu diskutieren?

    Genau.

    Arcoth schrieb:

    Und da genügt es nicht, Regeln zu finden, die man nur mit viel Mühe so auslegen kann. Auch gelebte Praxis hat einen gewissen normativen Wert.

    Große Mengen von Code die eine gewisse Regel verletzen, machen es unumgänglich, diese Regel anzupassen. Die Absicht des Komitees war es auch nicht, alle diese Regeln wider aller Umstände umzusetzen. Was ich auch bereits erwähnt habe. Das wird alles abgeschliffen werden.

    Das ist jedenfalls eine merkwürdige Einstellung, die nicht gerade Vertrauen erweckt. Ich kann mir nur schwer vorstellen, dass das die Position des gesamten Komitees sein soll. Ist das das gleiche Komtitee, dass sich für so unintuitive Namen wie unordered_map entschieden hat, um möglichst keinen bestehenden Code kaputt zu machen?


  • Mod

    camper schrieb:

    Arcoth schrieb:

    Niemand hat etwas gegen klare Sprache, aber die ist kein Selbstzweck und der Standard (hoffentlich) keine Spielwiese. Dass da schon Regeln waren, die regelmäßig gebrochen wurden, sollte demonstriert und nicht bloß behauptet werden.

    Dass Zeigerarithmetik nur über Arrays funktioniert, dürfte dir bekannt sein. [expr.add]/5. Was gibt es da zu diskutieren?

    Genau.

    ?

    Arcoth schrieb:

    Und da genügt es nicht, Regeln zu finden, die man nur mit viel Mühe so auslegen kann. Auch gelebte Praxis hat einen gewissen normativen Wert.

    Große Mengen von Code die eine gewisse Regel verletzen, machen es unumgänglich, diese Regel anzupassen. Die Absicht des Komitees war es auch nicht, alle diese Regeln wider aller Umstände umzusetzen. Was ich auch bereits erwähnt habe. Das wird alles abgeschliffen werden.

    Das ist jedenfalls eine merkwürdige Einstellung, die nicht gerade Vertrauen erweckt. Ich kann mir nur schwer vorstellen, dass das die Position des gesamten Komitees sein soll. Ist das das gleiche Komtitee, dass sich für so unintuitive Namen wie unordered_map entschieden hat, um möglichst keinen bestehenden Code kaputt zu machen?

    Warum sollte das Komitee einen Namen wählen, um Code nicht zu brechen? Der liegt doch sowieso in einem Namensraum?



  • Arcoth schrieb:

    Ich sehe auch überhaupt keinen Grund für ein eigenes memcpy .

    https://www.codeproject.com/Articles/1110153/Apex-memmove-the-fastest-memcpy-memmove-on-x-x-EVE


Anmelden zum Antworten