aliasing und alignment



  • Ist dein C-Style-Cast nicht auch ein reinterpret_cast<> ?



  • kpler schrieb:

    Ist dein C-Style-Cast nicht auch ein reinterpret_cast<> ?

    Guter Punkt. 😉
    Wie den Kommentaren zu entnehmen ist, spuckt der GCC ja auch hier keine Warnung mehr aus. http://ideone.com/GBhqV
    Auch scheint nur der alte C Compiler das überhaupt auszunutzen, und auch da komisch: http://ideone.com/w3CI0

    Alles sehr eigenartig.



  • lol jetzt ist er sogar bei reddit



  • Folgendes Code-Beispiel:

    #include <iostream>
    
    using namespace std;
    
    struct Test {
      int i;
      short s;
    };
    
    int main() {
      int i;
      float f = 3.11313;
      i = *(int*)&f;
      i = *reinterpret_cast<int*>(&f);
    
      int test[] = {1, 0};
      Test test2 = *reinterpret_cast<Test*>(test);
      std::cout << i << std::endl;
      std::cout << f << std::endl;
      std::cout << test2.i << std::endl;
    }
    

    Gibt bei mir diese Warnungen:

    ./test.cpp: In Funktion »int main()«:
    ./test.cpp:13:15: Warnung: Dereferenzierung eines Type-Pun-Zeigers verletzt strict-aliasing-Regeln [-Wstrict-aliasing]
    ./test.cpp:14:33: Warnung: Dereferenzierung eines Type-Pun-Zeigers verletzt strict-aliasing-Regeln [-Wstrict-aliasing]
    


  • Die Interpretation von 3.10/10 scheint nicht sonderlich einheitlich zu sein, wahrscheinlich (und hoffentlich) wird das umgeschrieben.



  • Was ist daran denn missverständlich? Es gibt laut Standard keinen definierten Weg um zu erreichen was du willst (zumindest afaik), außer evtl. einem memcpy() über einen char Buffer oder sowas (vorausgesetzt der char Buffer ist passend aligned etc). Das Ergebnis wird in der Regel aber wohl bestenfalls implementation defined sein.

    Nachdem ja schon nichtmal die exakte Größe eines simplen int definiert ist, ist mir auch nicht klar wie man das im Standard lösen sollte. Was genau sollte man da deiner Meinung nach umschreiben? Diese Regel ist sehr wichtig für die Performance des erzeugten Code, die einfach umzuschreiben ist nicht.



  • dot schrieb:

    memcpy()

    Sobald memcpy im Spiel ist, ist das Ganze eh vorbei, weil man dann auch einfach in einen std::vector<T> oder T[] oder sonst was kopieren kann, ab dem Punkt interessiert sich niemand mehr für aliasing oder alignment. Da braucht man auch keinen char Puffer zwischenzulegen. 😉

    Was daran nicht eindeutig ist? Na ja, ich habe hierzu bis jetzt vier Interpretationen hierzu, (wenn man alignment ignoriert und sich nur auf strict aliasing bezieht)
    Deiner Meinung nach ist alles illegal?

    #include <new>
    
    int main()
    {
      short buf[sizeof(double)];
      double d;
    
      buf[0] = 2; // accessed as short
    
      *(double*)buf = 345.465;
      d = *(double*)buf;
    
      double* d1 = reinterpret_cast<double*>(buf);
      *d1 = 32.234;
      d = *d1;
    
      double* d2 = new (buf) double;
      *d2 = 42.0323;
      d = *d2;
    }
    

    Und was wäre mit einem char Puffer? Laut 3.10/10

    If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined

    ...
    — a char or unsigned char type.

    Das schließt so eigentlich nicht mit ein, dass char[]s eine besondere Bedeutung bekommen. Der Weg funktioniert nur umgekehrt? Aber hey, wie soll placement new dann funktionieren? So wie ich das sehe, muss spätestens placement new ein neues Objekt einführen können.

    Und dass die Typen keine festen Größen haben hat eigentlich wenig mit aliasing zu tun, vielleicht noch mit alignment, aber auch daran scheint man in C++11 ja interessiert zu sein, was sich durch Dinge wie "alignof", "std::aligned_union" und "std::aligned_storage" zeigt.

    dot schrieb:

    Was genau sollte man da deiner Meinung nach umschreiben? Diese Regel ist sehr wichtig für die Performance des erzeugten Code,

    Das das Argument höre ich immer wieder. Aber wie wichtig kann die Regel schon sein, wenn weder VS noch GCC sie richtig durchziehen? VS ignoriert das scheinbar ganz, und der GCC packt gerade mal ne halbherzige Warnung raus. (Und auch das ziemlich unzuverlässig.) Die einzige Version, die ich dazu bringen konnte das wirklich auszunutzen, ist der 4.3.4 C Compiler.

    dot schrieb:

    die einfach umzuschreiben ist nicht.

    Mit einfach umschreiben meine ich nicht unbedingt strict aliasing aufzugeben. Aber die Regeln eindeutig machen. Was ist mit malloc() z.B.? Hier muss eindeutig Klarheit geschaffen werden.



  • cooky451 schrieb:

    dot schrieb:

    memcpy()

    Sobald memcpy im Spiel ist, ist das Ganze eh vorbei, weil man dann auch einfach in einen std::vector<T> oder T[] oder sonst was kopieren kann, ab dem Punkt interessiert sich niemand mehr für aliasing oder alignment.

    Also wenn du mich fragst, dann interessiert das dort sehr wohl...

    cooky451 schrieb:

    Was daran nicht eindeutig ist? Na ja, ich habe hierzu bis jetzt vier Interpretationen hierzu, (wenn man alignment ignoriert und sich nur auf strict aliasing bezieht)
    Deiner Meinung nach ist alles illegal?

    #include <new>
    
    int main()
    {
      short buf[sizeof(double)];
      double d;
      
      buf[0] = 2; // accessed as short
      
      *(double*)buf = 345.465;
      d = *(double*)buf;
     
      double* d1 = reinterpret_cast<double*>(buf);
      *d1 = 32.234;
      d = *d1;
     
      double* d2 = new (buf) double;
      *d2 = 42.0323;
      d = *d2;
    }
    

    Varianten 1 und 2 sind gleichwertig und beide undefiniert. Der Zugriff verletzt definitiv §3.10/10. Ich seh nicht, wo es da Spielraum für irgendwelche "Interpretationen" gibt; der Standard sagt klipp und klar dass das undefiniert ist.
    Das buf[0] = 2 ist ok und die Variante mit placement new ebenfalls (vorausgesetzt buf ist richtig aligned).
    Der Inhalt von buf ist im letzten Fall aber implementation defined.

    EDIT: Ok, das mit dem placement new ist in der Tat interessant. Ich würde jetzt mal sagen, dass du nach dem new nichtmehr auf buf zugreifen darfst.

    cooky451 schrieb:

    Und was wäre mit einem char Puffer? Laut 3.10/10

    If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined

    ...
    — a char or unsigned char type.

    Das schließt so eigentlich nicht mit ein, dass char[]s eine besondere Bedeutung bekommen. Der Weg funktioniert nur umgekehrt? Aber hey, wie soll placement new dann funktionieren? So wie ich das sehe, muss spätestens placement new ein neues Objekts einführen können.

    Was hat das mit placement new zu tun? Die Regel sagt nur dass der Compiler davon ausgehen muss, dass über einen char Pointer jedes mögliche Objekt modifiziert werden kann. Wenn du dagegen einen float Pointer hast, muss der Compiler nur davon ausgehen, dass dadurch nur ein float modifiziert werden kann.

    cooky451 schrieb:

    Und dass die Typen keine festen Größen haben hat eigentlich wenig mit aliasing zu tun, vielleicht noch mit alignment, aber auch daran scheint man in C++11 ja interessiert zu sein, was sich durch Dinge wie "alignof", "std::aligned_union" und "std::aligned_storage" zeigt.

    Es hat nichts mit aliasing zu tun, aber es hat sehr viel damit zu run, wie der Inhalt von deinem char Buffer aussieht. Ich ging davon aus dass der Hintergrund deiner Frage war, einen portablen und wohldefinierten Weg zu finden, um mit den Bytes von Objekten rumzuspielen. Den gibt es aber einfach nicht.



  • dot schrieb:

    Was hat das mit placement new zu tun?

    Was ich damit halt ausdrücken wollte war, dass es irgendeinen Weg geben muss Objekte zu "aliasen", und char[]s eigentlich keine Ausnahme sein können. (Was dann wieder dazu führt, dass man rein von der aliasing Regel her betrachtet eigentlich alle PODs Typen austauschen können muss.)

    Und malloc() gibts ja auch noch.



  • cooky451 schrieb:

    dot schrieb:

    Was hat das mit placement new zu tun?

    Was ich damit halt ausdrücken wollte war, dass es irgendeinen Weg geben muss Objekte zu "aliasen", und char[]s eigentlich keine Ausnahme sein können. (Was dann wieder dazu führt, dass man rein von der aliasing Regel her betrachtet eigentlich alle PODs Typen austauschen können muss.)

    Wo liest du denn das? Wenn du mich fragst sagt §3.10/10 ziemlich unmissverständlich und zweifelsfrei das genaue Gegenteil von dem was du da sagst. So wie ich das verstehe, steht dort: Alles außer char oder dem richtigen Typ macht bumm.

    cooky451 schrieb:

    Und malloc() gibts ja auch noch.

    Ja, das returned einen void*...



  • dot schrieb:

    Wo liest du denn das? Wenn du mich fragst sagt §3.10/10 ziemlich unmissverständlich und zweifelsfrei das genaue Gegenteil von dem was du da sagst.

    Der Gedankengang ist folgender:
    - placement new muss irgendwie funktionieren (zumindest mit char[]s)
    - +
    - char[]s kommen aber keine besondere Bedeutung zu
    - =
    - ? 🙂

    cooky451 schrieb:

    Und malloc() gibts ja auch noch.
    Ja, das returned einen void*...

    Ja, schon. Aber ein cast nach void* erlaubt ja nicht plötzlich alles. Wird also magisch der erste Cast genommen? Oder die erste Dereferenzierung? (Was ist wenn man auf den Speicher später noch mal über einen anderen Typen zugreift.)

    Und wie siehts hiermit aus?

    #include <new>
    
    int main()
    {
      short buf[sizeof(double) * 2];
    
      double* d1 = new (buf) double;
      d1[1] = 2.345;
      double d = d1[1];
    }
    


  • http://dbp-consulting.com/tutorials/StrictAliasing.html#toc_link10
    memcpy version, compliant to C and C++ specs and efficient



  • Wie gesagt, sobald memcpy ins Spiel kommt wird das ganze völlig uninteressant.



  • Wieso? Willst du nicht einfach die Bytes der Objekte lesen / schreiben?



  • pyhax schrieb:

    Wieso? Willst du nicht einfach die Bytes der Objekte lesen / schreiben?

    Es geht darum, auf einen Speicherbereich so zuzugreifen "als wären es" [typ]. Das schließt kopieren nicht mit ein, dann hat das keinen Sinn mehr. Dann ist es wahrscheinlich sogar performanter einfach char* zu nehmen. 😉
    (Und selbst wenn memcpy performanter sein sollte, das gehört irgendwie überhaupt nicht zum Thema. Natürlich funktioniert memcpy. Nie hat jemand etwas anderes behauptet.))


  • Mod

    Folgende Idee:

    template <typename T, typename U>
    T& binary_cast(U& v)
    {
        static_assert(std::is_trivially_copy_constructible<T>::value);
        static_assert(std::is_trivially_copy_constructible<U>::value);
    
        static_assert(sizeof(T) <= sizeof v);
    
        std::memmove( &reinterpret_cast<T&>(v), &v, sizeof(T) );
        return reinterpret_cast<T&>(v);
    }
    

    könnte funktionieren, wegen 3.9/2 - und weil an die gleiche Stelle gemoved wird, kann man hoffen, dass der Compiler das wegoptimiert.



  • cooky451 schrieb:

    pyhax schrieb:

    Wieso? Willst du nicht einfach die Bytes der Objekte lesen / schreiben?

    Es geht darum, auf einen Speicherbereich so zuzugreifen "als wären es" [typ].

    Das geht nicht, end of story.

    camper schrieb:

    [...] könnte funktionieren, wegen 3.9/2 - und weil an die gleiche Stelle gemoved wird, kann man hoffen, dass der Compiler das wegoptimiert.

    §3.9/2 garantiert aber doch auch nur, dass die Bytes beim zurückkopieren wieder den gleichen Wert ergeben und nicht wie die Bytes aussehen!?



  • dot schrieb:

    Das geht nicht, end of story.

    Verstehe ich nicht. Du hast doch oben selbst geschrieben, dass es mit placement new funktioniert. Was denn jetzt?



  • Placement new konstruiert ein Objekt, das ist alles. Wo genau hab ich denn geschrieben dass du damit auf definierte Art und Weise ein Objekt als von anderem Typ interpretieren kannst!?
    Wie gesagt, selbst das Speicherabbild von trivialen Typen wie double ist implementation defined. Was du vorhast kann also rein prinzipiell schon nicht allgemein gehen...


  • Mod

    dot schrieb:

    camper schrieb:

    [...] könnte funktionieren, wegen 3.9/2 - und weil an die gleiche Stelle gemoved wird, kann man hoffen, dass der Compiler das wegoptimiert.

    §3.9/2 garantiert aber doch auch nur, dass die Bytes beim zurückkopieren wieder den gleichen Wert ergeben und nicht wie die Bytes aussehen!?

    Betrachte

    int main()
    {
        short x[2] = { 42, 43 };
        int y;
        memcpy( &y, &x, sizeof y);
        cout << y << '\n';
    }
    

    gibt mir irgendeine Zahl z aus.
    (Annahme: sizeof(short[2])==sizeof(int))

    int main()
    {
        int y;
        cin >> y;
        short x[2];
        memcpy( &x, &y, sizeof x );
        cout << x[0] << ',' << x[1] << '\n';
    }
    

    Gebe ich hier das obige Ergebnis ein, erwarte ich als Ausgabe 42,43. Man kann es dem einzelnen memcpy ja nicht ansehen, ob es nun das ursprüngliche memcpy ist, oder das, das zum Rückkopieren dient. Das kann dann aber nur funktionieren, wenn ein einfaches memcpy bereits alle Typinformationen (auch in Hinblick auf das Aliasproblem in 3.10/10) des Ursprungsobjektes vernichtet.


Anmelden zum Antworten