Aliasing Probleme



  • Hallo,
    ich hab gerade ziemliche Probleme mit einem Datencontainer und Aliasing.
    Wenn ich das ganze mit dem g++ und O0 oder 01 bzw. -fno-strict-aliasing kompiliere, geht alles einwandfrei, mit O2 oder 03 ohne -fno-strict-aliasing bekomme ich merkwürdige Probleme mit Pointern.
    Nun caste ich desöftern Pointer hin-u. her, und zwar (soweit ich das überblicken kann) zwischen basis -u. abgeleiteten Klassen (beide nicht-virtuell), sowie zwischen void* und Klassen-Pointern (vorallem, weil teils mit malloc gearbeitet werden muss).
    Ich will nicht auf -fno-strict-aliasing angewiesen sein, deshalb würde mich mal interessieren, was genau ich denn nun beachten muss, um Probleme zu vermeiden. Meinem Verständnis nach sollten Casts zwischen verwandten Klassen ja in Ordnung sein, ebenso Casts von void* auf Klassen (schon weil z.B. malloc void* ausspucht [ja, ich weiß, dass malloc in C++ zu vermeiden ist]).
    Leider spuckt der g++ auch keine passenden Warnungen aus (was er bei trivalen Beispielen zum Thema Aliasing ja durchaus tut).



  • Etwas Code und ein dazugehörender Fehler würde helfen.

    Aber schon mal so vorweg: "malloc + Klassen" klingt schon mal nicht gut.



  • Da ich das Problem noch nicht wirklich klar lokalisieren kann, kann ich auch keinen Code posten (und ich werde nicht die gesamten 1000 Zeilen des Containers + 1000 Zeilen der Unit-Tests veröffentlichen).
    malloc wird benutzt, um einen Memory-Pool zu allokieren, in den dann die eigentlichen Instanzen gepackt werden.

    Mir gehts ja generell ums Aliasing - da mir eben noch nicht ganz klar ist, woher überhaupt Probleme kommen könnten.


  • Administrator

    Es ist schwer, da irgendetwas zu sagen, ohne grundsätzliche Problemstellung. Normalerweise funktionieren Zeigern, sofern man nicht ganz seltsame Dinge macht 😉
    Du sagst, du hättest Up- und Downcasts? Bei Downcast verwendest du womöglich dynamic_cast . Hast du in der Basisklasse mindestens eine virtuelle Funktion? Mindestens den Destruktor virtual gemacht? Denn dies ist für einen erfolgreichen dynamic_cast nötig.

    Grüssli



  • Ich benutze nur static_cast und die Klassen besitzen auch keine virtuellen Funktionen (dürfen sie auch nicht).

    edit: und das Problem wird noch dadurch verschärft, dass ich es nur mit dem g++ 4.3.1 reproduzieren kann, nicht aber mit einem 4.1 oder 4.2 (auch nicht mit -fstrict-aliasing)



  • Ein Cast von void* nach T ist genau dann erlaubt, wenn der void* durch einen Cast von T nach void* erzeugt wurde.

    Konkret:

    class Base {};
    class Derived : public Base {};
    
    void foo()
    {
        Derived d;
        Derived* pd = &d;
        void* pv = pd;
    
        Derived* pd2 = static_cast<Derived>(pv); // OK
        Base* pb = static_cast<Base>(pv); // FEHLER!!!
    }
    


  • Hast du die Probleme auch wenn du operator new verwendest zum allokieren?
    Ich weiss nicht ob malloc garantiert das richtige alignment fuer alle Klassen zu liefern. operator new muesste das aber tun...

    was sind denn die genauen Probleme die auftreten?

    wobei ich sagen muss, ich kenne "-fno-strict-aliasing" garnicht. Was macht es denn genau? Hab das jetzt auf die schnelle in der gcc docu nicht gefunden. Waere interessant zu wissen was es genau tut 😉

    [edit]
    Ach, doch noch gefunden.
    Sieht doch dann ziemlich deutlich aus, oder?

    Allows the compiler to assume the strictest aliasing rules applicable to the language being compiled. For C (and C++), this activates optimizations based on the type of expressions. In particular, an object of one type is assumed never to reside at the same address as an object of a different type, unless the types are almost the same.

    das erklaert IMHO ziemlich deutlich warum du mit -fno-strict-aliasing die gewuenschten resultate hast - wenn du so mit rohem speicher umgehst.



  • Shade Of Mine schrieb:

    was sind denn die genauen Probleme die auftreten?

    Naja, wie so Pointer-Problemchen halt aussehen - es passiert nicht das, was passieren soll. Elemente in den Datencontainer einfügen hat keinen Effekt, die interne Integrität wird zerschossen (Pointer ins Nirvana) etc.

    Shade Of Mine schrieb:

    wobei ich sagen muss, ich kenne "-fno-strict-aliasing" garnicht. Was macht es denn genau? Hab das jetzt auf die schnelle in der gcc docu nicht gefunden. Waere interessant zu wissen was es genau tut 😉

    Das schaltet halt das strenge Aliasing aus.
    Laut Standard dürfen zwei Pointer inkompatiblen Typs nicht auf den selben Speicher verweisen.
    Beim Optimieren mit striktem Aliasing geht der Compiler bei Pointern unterschiedlichen Typs davon aus, dass sie auf unterschiedlichen Speicher zeigen und hat damit mehr Möglichkeiten, den Code um zu sortieren, weil der Zugriff auf einen Pointer keine Seiteneffekte auf andere haben sollte.

    Edit: meine Frage hat ursprünglich auch primär auf das Problem mit den abgeleiteten Klassen gezielt. Die malloc Geschichte ist einmalig beim Initialisieren im Code und wenn das nicht funktionieren würde, sollte (naja...) mir eigentlich alles um die Ohren fliegen.

    Ich hab keine Aussage gefunden, nach der das Aliasing auch bei Klassen einer Hierarchie problematisch ist, aber imho wäre es ja reichlich irrsinnig, wenn dem so wäre.



  • Also ich hab jetzt kein malloc mehr drin und die Probleme sind tatsächlich weniger geworden, aber leider auch nicht ganz verschwunden.

    Mein Container hat eine size() Funktion, die die Entfernung zwischen begin() und end() Iterator berechnet.
    Nach dem Einfügen eines Elements und nachfolgender Ausgabe von size() ist die immernoch 0, obwohl das Element scheinbar eingetragen wurde.
    Nach dem Einfügen eines zweiten Elements und nachfolgender Ausgabe von size() ist sie plötzlich (korrekterweise) 2.
    Und nun wirds ganz lustig: Rufe ich nach dem ersten einfügen size() zweimal hintereinander auf, bekomm ich erst 0 und dann 1 😮
    Füge ich in die size() z.B. direkt eine Ausgabe ein, funktioniert auch alles so wies soll.
    Und wie gesagt, nur wenn der g++ mit striktem Aliasing optimiert. Da wird irgendwie der Code inkorrekt umgebaut.

    Die einzigen Casts, die ich in dem Prozess finden kann, sind beim Einfügen eines Elements, eben die vorher erwähnten Upcasts mit static_cast von Basis -zu abgeleiteter Klasse. Das ist nötig, weil die Basisklasse nur die Verlinkung der Elemente zur Verfügung stellt, die abgeleitete Klasse enthält noch das eigentliche Datenelement (die Konstruktion ist nötig, um ein End-Element zu realisieren, das selbst keine Instanz der Daten enthält).

    Ich versuche daraus ein Beispiel zu konstruieren, aber das ist nicht so einfach (vorallem so, dass der Fehler auch auftritt).



  • Zeig doch erst mal nur deine size()-Funktion...
    Die sollte ja eigtl nur

    (last-first)/sizeof(T)
    (ggf. noch nen cast von fist bzw last nach size_t - weiß nicht, ob der nötig ist...)

    lauten, oder!?

    bb



  • nö, die lautet:

    size_type size() const{
    	return std::distance(this->begin(), this->end());
    }
    

    Und auch eine selbstgeschriebene Schleife ändert da nichts.

    Edit: Übrigens habe ich gerade festgestellt, dass ich die Begriffe Up u-Downcast verdreht habe. Mit Upcast meinte ich Basisklasse->Abgeleitete Klasse.



  • maximAL schrieb:

    nö, die lautet:

    size_type size() const{
    	return std::distance(this->begin(), this->end());
    }
    

    Und auch eine selbstgeschriebene Schleife ändert da nichts.

    na gut - das is natürlich hübscher ^^ erzeugt aber vrmtl den gleichen code 😛
    hmm... na dann musst du wohl wirklich mal nen bissl mehr quelltext zeigen ^^

    bb



  • Entweder du zeigst uns Code wie du Zeiger benutzt od wo die Fehler liegen (debugging) oder das Raetselraten geht weiter. Vielleicht helfen auch Kompilerwarnungen wie -Wall oder -Weffc++.



  • also einfach mal so aus dem Code gepflückt:

    iterator insert(iterator loc, const value_type& val){
      element_base_type *inserted = (this->m_free_end_element.get_next());
      static_cast<element_type*>(inserted)->set_value(val);
    
      this->move(loc.get_element(), inserted);
    
      return iterator(inserted);
    }
    
    void move(element_base_type *loc, element_base_type *element){
      element->get_prev()->set_next(element->get_next());
      element->get_next()->set_prev(element->get_prev());
    
      element->set_prev(loc->get_prev());
      element->set_next(loc);
    
      loc->get_prev()->set_next(element);
      loc->set_prev(element);
    }
    

    Der Punkt ist natürlich, dass das Problem nicht einfach hier auftritt. Der Code wird an X Stellen benutzt und funktioniert meisstens wunderbar. Nur hier und da macht der Compiler was beim Optimieren kaputt, da spielen auch noch alle möglichen Randbedingungen mit rein.



  • Warum redest du dann vom "einfachen hinzufügen" und zeigst jz nur funktionen zum verschieben und in der irgendwo (jedenfalls nicht speziell vorn oder hinten - so eine fkt sollte es doch aber schon geben!? ^^) einfügen?

    bb



  • Insert fügt einen wert vor dem iterator ein. Das move ist nötig, weil der Container schon einen Pool an belegten und nicht belegten Objekten besitzt.



  • mini-update: Werden die ursprünglichen Klassen-Deklarationen von element_base_type und element_type mit __attribute__((__may_alias__)) versehen, so funktioniert wieder alles wie gewünscht. Da es auch nur Casts zwischen diesen beiden Typen gibt ( static_cast<element_type*>(element_base_type_pointer) ), scheint der Compiler da wirklich bei beiden Typen das Aliasing durcheinander zu bringen, was eigentlich nicht passieren sollte, da sie verwandt sind.
    Ich werd nochmal versuchen, einen aktuelleren gcc 4.3.3 aufzutreiben, um auszuschliessen, dass das ein Bug nur von 4.3.1 ist...


Log in to reply