[CLOSED] [boost.date_time] Compiler-Bug?



  • Hallo alle zusammen

    Im Zusammenhang mit boost.date_time habe ich eine neue Klasse in mein Projekt eingeführt, die ein wenig Datums-basierte Arithmetik beinhaltet (z.B. Stand von Datum 1 minus Stand von Datum 2 berechnen). Auf meiner Entwicklungsplattform in den Unit-Tests klappt alles reibungslos und auch auf 2 von 3 (Embedded-)Zielplattformen.

    Um einmal etwas Kontext zu schaffen, hier der komplette Code der Klasse:

    ArtificialLoadProfile.h:

    #ifndef CH_BEO_EMC_FORMAT_BASE_ARTIFICIALLOADPROFILE_H_
    #define CH_BEO_EMC_FORMAT_BASE_ARTIFICIALLOADPROFILE_H_
    
    #include <boost/date_time/posix_time/posix_time_types.hpp>
    #include <boost/shared_ptr.hpp>
    #include <vector>
    
    namespace ch {
    namespace beo {
    namespace emc {
    namespace entity {
    namespace protocol {
    class BillingData;
    typedef boost::shared_ptr<class LoadProfile> LoadProfilePtr;
    }
    }
    namespace format {
    namespace base {
    
    class ArtificialLoadProfile {
    public:
        ArtificialLoadProfile(const boost::posix_time::time_duration &, const std::vector<entity::protocol::BillingData> &);
        ~ArtificialLoadProfile();
        void generate();
        operator entity::protocol::LoadProfilePtr() const;
    private:
        const boost::posix_time::time_duration timeStep, tolerancy;
        const entity::protocol::LoadProfilePtr result;
        std::vector<entity::protocol::BillingData> data;
    };
    
    }
    }
    }
    }
    }
    
    #endif /* CH_BEO_EMC_FORMAT_BASE_ARTIFICIALLOADPROFILE_H_ */
    

    ArtificialLoadProfile.cpp:

    #include "ch/beo/emc/format/base/ArtificialLoadProfile.h"
    #include "ch/beo/emc/entity/data/Quantity.h"
    #include "ch/beo/emc/entity/protocol/BillingData.h"
    #include "ch/beo/emc/entity/protocol/LoadProfile.h"
    #include <boost/foreach.hpp>
    #include <map>
    
    using namespace boost;
    using namespace boost::posix_time;
    using namespace ch::beo::emc::entity::data;
    using namespace ch::beo::emc::entity::protocol;
    using namespace std;
    
    namespace ch {
    namespace beo {
    namespace emc {
    namespace format {
    namespace base {
    
    namespace {
    void applyTolerancy(ptime &timestamp, time_duration tolerancy) {
        time_duration timeOfDay(timestamp.time_of_day());
        time_duration::tick_type offset = timeOfDay.total_milliseconds() % tolerancy.total_milliseconds();
        time_duration correction = milliseconds(offset);
        if (correction < tolerancy / 2) {
            timestamp -= correction;
        } else {
            timestamp += (tolerancy / 2 ) - correction;
        }
    }
    
    Quantity operator-(const Quantity &lhs, const Quantity &rhs) {
        return Quantity(lhs.getValue() - rhs.getValue(), lhs.getUnit(), lhs.getDescriptor());
    }
    
    template<class T>
    Quantity operator*(T factor, const Quantity &quantity) {
        return Quantity(quantity.getValue() * factor, quantity.getUnit(), quantity.getDescriptor());
    }
    }
    
    ArtificialLoadProfile::ArtificialLoadProfile(const time_duration &timeStep, const vector<BillingData> &data) :
        timeStep(timeStep), tolerancy(seconds(30)), result(new LoadProfile()), data(data) {
    }
    
    ArtificialLoadProfile::~ArtificialLoadProfile() {
    }
    
    void ArtificialLoadProfile::generate() {
        map<Descriptor, pair<ptime, QuantityPtr> > currentData;
        BOOST_FOREACH(const BillingData &billingData, data) {
            ptime timestamp(billingData.getTimestamp());
            applyTolerancy(timestamp, tolerancy);
            BOOST_FOREACH(const State &state, billingData.getStates()) {
                // inner Loop
                if (dynamic_pointer_cast<Quantity> (state.getValue())) {
                    QuantityPtr quantity(static_pointer_cast<Quantity> (state.getValue()));
                    map<Descriptor, pair<ptime, QuantityPtr> >::iterator it(currentData.find(quantity->getDescriptor()));
                    if (it != currentData.end()) {
                        time_duration difference(timestamp - it->second.first);
                        time_duration::tick_type numerator(timeStep.total_milliseconds());
                        time_duration::tick_type denominator(difference.total_milliseconds());
                        Quantity consumption(*quantity - *it->second.second);
                        consumption = (static_cast<double> (numerator) / denominator) * consumption;
                        unsigned int previousSize = result->getConsumptions().size();
                        for(ptime t(it->second.first); t + timeStep <= timestamp; t += timeStep) {
                            result->getConsumptions().push_back(Consumption(t, t + timeStep, consumption));
                        }
                        if (result->getConsumptions().size() > previousSize) {
                            currentData.erase(it);
                        }
                    }
                    currentData.insert(make_pair(quantity->getDescriptor(), make_pair(timestamp, quantity)));
                }
            }
        }
        sort(result->getConsumptions().begin(), result->getConsumptions().end());
    }
    
    ArtificialLoadProfile::operator LoadProfilePtr() const {
        return result;
    }
    
    }
    }
    }
    }
    }
    

    Der Kommentar "Inner Loop" markiert die Stelle, die mich hier seit einigen Tagen ärgert. Der Code funktioniert korrekt für den ersten Schleifendurchlauf und generiert das erwartete Resultat. Am Ende der ersten Iteration, also wenn ich zu unterst im Loop-Body die Werte aller Members ausgebe (timeStep, tolerancy, ...), ist alles wie erwartet. Zu Beginn der zweiten Iteration jedoch sind alle Werte verpfuscht, sogar die Werte der Konstanten! Wenig später verabschiedet sich das Programm mit einer "segmentation fault"-Message.

    Den boost.date_time-spezifischen Code durch Konstanten zu ersetzen (z.B. "difference.total_milliseconds()" ist in den bearbeiteten Fällen immer 900000) führte dazu, dass das Programm korrekt durchlief. Wieso die Anomalie aber beim Wechsel in die nächste Iteration auftreten sollte, wenn diese Methoden das Problem sind, erschliesst sich mir nicht.

    Meine Frage deshalb: Stellt boost.date_time irgendwelche Anforderungen an den Compiler (Datentypen, etc.), die diese Anomalie auslösen könnten? Ich weiss, dass die Library 64-Bit Zahlen verwendet, was meinen Compiler vielleicht zum Durchdrehen veranlasst. Die time_traits sind zwar konfigurierbar, aber mit einer 32-Bit Zahl kann man keine Millisekunden-Zeitstempel kreieren, nicht wahr?

    Danke für irgendwelche Ideen und Hinweise jeglicher Art
    Kessi


  • Mod

    Wenn man was von Compilerfehlern behauptet, dann wäre es schön, wenn man ein compilierbares Beispiel liefern würde. Allgemein ist dies aber eine sehr starke Behauptung die wohl begründet sein will, weil es quasi nie zutrifft. So auch hier: Klingt eher danach als hättest du falschen Code geschrieben der ungültige Zugriffe macht. Mit einem Debugger solltest du den Fehler schnell finden können.



  • SeppJ schrieb:

    Wenn man was von Compilerfehlern behauptet, dann wäre es schön, wenn man ein compilierbares Beispiel liefern würde. Allgemein ist dies aber eine sehr starke Behauptung die wohl begründet sein will, weil es quasi nie zutrifft. So auch hier: Klingt eher danach als hättest du falschen Code geschrieben der ungültige Zugriffe macht. Mit einem Debugger solltest du den Fehler schnell finden können.

    Nun ja, kompilierbar ist es definitiv, der Fehler tritt zur Laufzeit auf. Mein ganzes Projekt hochuzuladen würde hier aber vermutlich nicht viel nützen, da die Zielplattform nicht zur Verfügung steht. Debugging, wie gesagt, zeigt, dass der ungültige Zustand (mit veränderten Konstanten, etc.) beim Übergang von der ersten in die zweite Iteration der äusseren Schleife eingenommen wird:

    BOOST_FOREACH(const BillingData &billingData, data) { /* ... */ }
    

    Das einzige, was dazwischen meines Wissens passieren sollte, ist ein Inkrement auf dem Iterator. Es ist ja nicht so, dass ich im Code mit Pointern und Arrays hantiere. Aufgrund der Symptome ging meine erste Vermutung auch in Richtung ungültige Zugriffe. Dagegen spricht aber, dass der Fehler sehr deterministisch ist. Auch wenn ich die Speicherverhältnisse verändere (weniger Entitäten auf Heap/Stack, etc.), tritt der Fehler nicht zufällig auf, sondern immer exakt beim zweiten Schleifendurchlauf.

    Ich möchte also keinesfalls braven Comiler-Entwicklern unrechtmässig zu Nahe treten 🙂 . Vielmehr war mein Post an Entwickler gerichtet, die Erfahrung mit den Plattform-spezifischen Caveats der boost.date_time Library haben, die meinen Compiler hier vielleicht ins Schwitzen gebracht haben.



  • KessiMC schrieb:

    SeppJ schrieb:

    Wenn man was von Compilerfehlern behauptet, dann wäre es schön, wenn man ein compilierbares Beispiel liefern würde. Allgemein ist dies aber eine sehr starke Behauptung die wohl begründet sein will, weil es quasi nie zutrifft. So auch hier: Klingt eher danach als hättest du falschen Code geschrieben der ungültige Zugriffe macht. Mit einem Debugger solltest du den Fehler schnell finden können.

    Nun ja, kompilierbar ist es definitiv, der Fehler tritt zur Laufzeit auf. Mein ganzes Projekt hochuzuladen würde hier aber vermutlich nicht viel nützen, da die Zielplattform nicht zur Verfügung steht.

    Versuche ein minimales Beispiel zu basteln, das immer noch mit dem selben Fehler abschmiert. Und zwar indem du Stück für Stück Code durch Dummy-Code ersetzt, und nur das drinnen lässt was nötig ist um den Fehler "zu erhalten".

    Oft genug findet man dabei den Fehler schon, nämlich wenn man merkt dass wenn man Code X durch nen Dummy ersetzt der Fehler weg ist. Dann sieht man sie X näher an, und schwupps, Fehler gefunden.

    Manchmal natürlich aber auch nicht.

    Falls nicht hast du dann aber ein Beispiel, das vermutlich ohne die Zielplattform läuft, und klein genug ist dass du es hier posten kannst. Bzw. auch klein genug, dass man es sich mit vertretbarem Aufwand genauestens im Debugger ansehen kann.



  • KessiMC schrieb:

    Meine Frage deshalb: Stellt boost.date_time irgendwelche Anforderungen an den Compiler (Datentypen, etc.), die diese Anomalie auslösen könnten? Ich weiss, dass die Library 64-Bit Zahlen verwendet, was meinen Compiler vielleicht zum Durchdrehen veranlasst. Die time_traits sind zwar konfigurierbar, aber mit einer 32-Bit Zahl kann man keine Millisekunden-Zeitstempel kreieren, nicht wahr?

    Ja !
    Je nach Auflösung der Zeit-Einheit kann es passieren, dass zwar X-tausend CPU-Takte vergangen sind, aber die boost::date_time Zeitdifferenz trotzdem 0 ergibt. Damit fliegt man auch unter Windows schön auf die Fresse, insbesondere wenn man durch die Zeit-Differenz teilen möchte.
    Schonmal gedacht das denominator = 0 sein könnte.

    Wäre schön, wenn du die Details deiner fehlerhaften Embedded-Platform mitteilen würdest.
    Compiler, Architektur etc., evtl. sogar µlibc statt glibc usw.

    Prinzipiell das perfekte Beispiel für einen Unit-Test. Einfach weitere Test-Cases erschaffen, bist du das Problem eingekreist hast.



  • Das ist ein guter Punkt, ich werde das Ganze extrahieren - der Algorithmus für sich ist ja eine ziemlich autonome Klasse - und versuchen, den Fehler in einem separaten Programm zu reproduzieren.

    Wenn der Fehler wirklich so deterministisch ist, wie er sich bisher verhalten hat, werde ich ihn dort ebenfalls isolieren können. Ich werde den entsprechenden Code posten, sobald ich das durchgespielt habe.

    Danke vorerst einmal und Gruss
    Kessi



  • Hallo nochmals

    Ich habe den Algorithmus jetzt isoliert und in ein eigenes, kleines Eclipse-Projekt inklusive makefile gepackt. Für den Build müssen (in Eclipse oder Shell) folgende Variablen definiert sein:

    CC=gcc
    CXX=g++
    LN=g++
    PREPROCESSOR_SYMBOLS=''
    BOOST_HEADERS=<Pfad zu Boost include, bei mir: C:/PROGRA~1/C++/boost/boost_1_43_0/target/mgw44-x86/include/boost-1_43>
    BOOST_LIBS=<Pfad zu Boost lib, bei mir: C:/PROGRA~1/C++/boost/boost_1_43_0/target/mgw44-x86/lib>
    BOOST_LIBRARY_SUFFIX=<Boost Versions-Suffix, bei mir: -mgw44-mt-d-1_43>
    

    Das Projekt kann unter folgendem Link heruntergeladen werden:
    http://www.2shared.com/file/0W-CsIvK/uc-8410-artificial-load-profil.html?

    Ich habe das Programm mit ddd und gdb debuggt - der Absturz tritt in diesem Fall im (Compiler-generierten) Kopier-Konstruktor von Consumption auf. Zum Absturzzeitpunkt wurden bereits zwei Verbräuche in die Liste eingetragen und 5 States verarbeitet (wovon zwei Differenzen (=Verbräuche)) generiert wurden. Beim Eintragen des dritten Verbrauchs tritt der segmentation fault auf.
    http://www.2shared.com/photo/H_j7bfnq/debug-output.html

    Danke also und Gruss
    Kessi

    PS:
    Es handelt sich bei der Zielplattform um den Typ "MOXA UC-8410-LX version 1.1.3" mit folgender Toolchain:
    "arm-linux-g++ (GCC) 4.2.1
    Copyright (C) 2007 Free Software Foundation, Inc.
    Dies ist freie Software; die Kopierbedingungen stehen in den Quellen. Es
    gibt KEINE Garantie; auch nicht für MARKTGÄNGIGKEIT oder FÜR SPEZIELLE ZWECKE."



  • Der Hersteller hat die Anfrage mittlerweile entgegen genommen, aber noch keine Rückmeldung geliefert. Ich hoffe immer noch, dass ich wirklich irgendwo etwas übersehen habe und nicht die ganze Plattform fehlerhaft ist...



  • Ich hoffe nicht, dass jemand Zeit darauf verschwendet sich den Code
    anzukucken...
    Insbesondere nicht die Entwickler des Compilers...

    Lächerlich!!!

    hustbaer schrieb:

    Versuche ein minimales Beispiel zu basteln, das immer noch mit dem selben Fehler abschmiert

    .

    1043 Zeilen Code!!!
    Du hast ja wohl einen an der Klatsche...



  • Ja nee.... schrieb:

    1043 Zeilen Code!!!
    Du hast ja wohl einen an der Klatsche...

    Jetzt mach mal halblang. Es geht immer noch um die 30 Zeilen Code in ArtificialLoadProfile::generate. Um ein lauffähiges Codebeispiel posten zu können, habe ich einfach die beteiligten Entity-Objekte mitgenommen. Ich hoffe nicht, dass jemand kontrolliert, ob ich in diesen Entities meine Getter und Setter richtig programmiert habe 😉 .

    Aber es hilft nichts. Ich denke nicht, dass ich im ArtificialLoadProfile einen offensichtlichen Fehler begangen habe, sonst hätte mittlerweile jemand (sinnvoll) geantwortet. Und ohne die Zielplattform könnt ihr mir natürlich bei Plattform-spezifischen Problemen nicht weiterhelfen.

    Danke also trotzdem und Gruss
    Kessi



  • KessiMC schrieb:

    Ja nee.... schrieb:

    1043 Zeilen Code!!!
    Du hast ja wohl einen an der Klatsche...

    Jetzt mach mal halblang. Es geht immer noch um die 30 Zeilen Code in ArtificialLoadProfile::generate.

    Wenn es um 30 Zeilen Code geht, dann poste auch die 30 Zeilen.
    Irgendwie hab ich das Gefühl, du glaubst auch dass der Fehler (irgendwo)
    bei die liegt und hoffst, dass sich jemand die Mühe macht und sich alles
    ankuckt... Ui... Compiler-Bug... Muss ich unbedingt verifizieren...
    Ich vermute (nahezu) keiner der halbwegs kompetenten Foren-User glaubt
    wirklich an einen Compiler-Bug!

    Wenn der Rest nicht nötig ist um den Fehler zu reproduzieren, dann rationalisier
    ihn weg! Dann wird dir sicherlich jemand helfen...



  • Wenn mir als Entwickler jemand einen "Bugreport" schreiben würde (spricht: über 1000 Zeilen Code mit der Beschreibung: "Bug, sucht mal!"), würde ich dem sicher nicht nachgehen. Jeder Aufwand wäre verschwendete Zeit.

    Wie schon erwähnt: Minimiere den Code auf ein Minimum und wende die üblichen Debug-Techniken an. Bei einem offensichtlichen Beispiel mit fehlerhafter Semantik kannst du dann auch behaupten, dass ein Compiler-Bug vorliege, aber nicht einfach mal so. Geh immer davon aus, dass du den Fehler machst, bis du das Gegenteil beweisen (und nicht vermuten) kannst.



  • Mann, was für ein Troll bist du denn 😛 . Ich wurde aufgefordert, den Rest auch hochzuladen, damit's kompilierbar wird. Ich hätte z.B. die Klasse "Descriptor", die zwei strings enthält, im Algorithmus durch einen einfachen string ersetzen können, hielt es aber für sinnvoller, sie einfach mitzusenden.

    "Compiler-Bug" heisst der Titel des Posts übrigens darum, weil der Hersteller uns bestätigt hat, dass sie bereits viele solche Support-Anfragen erhalten haben - so funktioniert z.B. die vorinstallierte Version von openssl auf der Plattform nicht korrekt und Apache stürzt beim Einschalten von HTTPS mit einem klassischen "segmentation fault" ab. Vielleicht wäre "Fehlerhafte Plattform" ein besserer Titel gewesen, aber ich denke, ich sollte den Troll an dieser Stelle nicht weiter füttern.

    Damit aber niemand denselben Fehler wie "Ja nee..." macht, noch einmal explizit: Falls euch ein Fehler oder eine unsichere Praktik in der Methode "ArtificialLoadProfile::generate" auffällt, z.B. in der Verwendung der Iteratoren und der "erase"-Methode, dann klärt mich doch bitte auf. Den Rest bitte nicht durchlesen, die restlichen Klassen sind nur dazu da, falls ihr das ganze kompilieren möchtet um zu sehen, dass der Code auf eurer Plattform ebenfalls laufen wird. In der Zwischenzeit debugge ich mal fröhlich weiter.

    Beste Grüsse



  • Sinnvoll wäre wenn DU versuchst Stück für Stück Code rauszunehmen, so lange bis du nichts mehr wegnehmen kannst ohne dass der Fehler verschwindet.
    Das ist dein minimales Beispiel.
    Eine Klasse die man durch einen std::string ersetzen kann hat darin nichts verloren, egal wie klein diese ist.



  • Okay, ich denke, nach all dem hin und her wird sich niemand mehr den eigentlichen Code ansehen - egal, was ich noch hochlade. Deshalb würde ich den Thread hiermit gerne schliessen.

    Danke an alle, die sich die Mühe gemacht haben und Gruss
    Kessi



  • KessiMC schrieb:

    Okay, ich denke, nach all dem hin und her wird sich niemand mehr den eigentlichen Code ansehen - egal, was ich noch hochlade.

    Irgendwie hab ich das Gefühl du bist zu faul den Code auf ein Minimal-
    Beispiel zu reduzieren oder machst es aus anderen Gründen nicht.

    Ich bin mir sicher, dass SeppJ, Nexus, hustbaer, ich oder irgendein anderer
    mal einen Blick drauf werfen würde. Ich kann nicht garantieren, dass mir
    etwas auffällt, aber es gibt hier einige kompetente Nutzer, die gerne helfen.
    (Vielleicht findest du den Fehler beim Minimieren ja selbst...)

    Und solange du niemanden (persönlich) angreifst, was du ja soweit ich sehe
    (noch) nicht gemacht hast, sind hier sicherlich auch jetzt noch hilfsbereite
    Nutzer zu finden.

    Gruß,
    XSpille



  • jop
    genau

    klar 100 zeilen sind viel, aber wenns dadurch erst kommt
    andere augen sehen vllt eher was

    also ich finds auch was blöd was die "grossen" hier machen, einfach mal rein gucken tut nicht weh...

    edit: wobei ich gucke grad rein, kompilierbar wars nicht sofort (musste irgendwo aus nem hash ein std::hash machen oder halt boost)

    und die methode wo du hilfe brauchst ist abolut unlesbar
    keine komentare, keine leerzeile mal dazwischen, so viele buchstaben können meine augen doch nich verarbeiten



  • Ich habe das Programm mit ddd und gdb debuggt - der Absturz tritt in diesem Fall im (Compiler-generierten) Kopier-Konstruktor von Consumption auf.

    Dann würde ich der Klasse mal einen eigenen Kopierkonstruktor spendieren.
    Der Rest ist so vage, das man da wirklich nur raten kann.

    Der Debuggeroutput ist auch relativ nichtssagend.
    Evtl. zu wenig Speicher auf der Plattform?



  • Hallo nochmals

    Danke für den Input. Grund dafür, nicht alle Klassen zu eliminieren, war unter anderem, eine möglichst ähnliche Situation wie im Original zu schaffen (z.B. eben Kopierkonstruktor). Dass der Fehler effektiv dort lag, konnte ich mir nicht vorstellen, aber eventuell würde das ganze zur abstürzenden Speichersituation beitragen.

    Ich habe die boost.date_time-Operationen aus ArtificialLoadProfile::generate, die mir vom Gefühl her verdächtig vorkamen, in einem anderen Beispiel genutzt und es treten in der Tat ähnliche Anomalien auf. Dazu siehe bitte:
    http://www.c-plusplus.net/forum/283048

    Wer sich diesen Thread durchgelesen hat, wird die Berechnung des Multiplikators aus "ArtificialLoadProfile::generate" noch bekannt vorkommen. Sorry an alle, denen der Build nicht auf Anhieb geklappt hat. Die Files stammen von einer Multi-Plattform Hudson Build-Umgebung, und offensichtlich habe ich nicht alle Umgebungs-Variablen angegeben.

    Da ich diesen Thread formell geschlossen habe (gibt's hier dazu eigentlich einen Button 🙂 ? ), wären mir künftige Ideen und Vorschläge im neuen Thread lieber:
    http://www.c-plusplus.net/forum/283048

    Danke also nochmals und Gruss
    Kessi



  • KessiMC schrieb:

    Danke für den Input. Grund dafür, nicht alle Klassen zu eliminieren, war unter anderem, eine möglichst ähnliche Situation wie im Original zu schaffen (z.B. eben Kopierkonstruktor).

    Ich kack mir gleich inne Buchse vor lachen... 🤡

    Wenn man den Copy-Constrictor wegrationalisiert hätte, dann hätte man nicht
    gewußt, dass dieser schuld ist 🙄

    Beim nächsten mal nicht vergessen: Minimieren keinen Quellcode, ansonsten könnte der Fehler ja
    behoben werden 👍


Log in to reply