Visual C++ 2005 - Bug in STL?



  • Hallo zusammen

    Ich bin heute auf ein seltsames Problem gestossen. Es wurde bei std::copy, aus mir unerklärlichen Gründen, eine Assertion ausgelöst (in der Debug-Version). In der Release-Version stürzt das Programm einfach ab. Es geht dabei ums Kopieren von einem Iterator zu einem Pointer. Das Problem erwies sich als durch eine eigene Copy-Funktion, oder durch explizite Angabe der Templateparameter, lösbar.
    Ich habe die Umstände, wie sie in meinem Projekt sind, nachgestellt. Ohne die Verschachtelungen tritt der Fehler nämlich nicht auf.
    Hat jemand eine Erklärung für das Problem?

    Gruss

    Stefan

    #include <iostream>
    #include <string>
    #include <vector>
    
    struct A
    {
    	A(): v(100)
    	{
    	}
    
    	const std::vector<double>& get() const
    	{
    		return v;
    	}
    
    	std::vector<double> v;
    };
    
    struct B
    {
    	B(): v(5)
    	{
    	}
    
    	const std::vector<A>& get() const
    	{
    		return v;
    	}
    
    	std::vector<A> v;
    };
    
    struct Array
    {
    	int size;
    	double data[1];
    };
    
    template<class T, class U>
    inline void Copy(T a, T b, U c)
    {
    	while(a != b)
    	{
    		*c = *a;
    		++a;
    		++c;
    	}
    }
    
    int main()
    {
    	using namespace std;
    
    	Array* x = (Array*)malloc(sizeof(int) + sizeof(double) * 100);
    
    	Array** h = &x;
    
    	B b;
    
    	const A &a = b.get()[2];
    
    	const vector<double> &v = a.get();
    
    	// fehler
    	copy(v.begin(), v.end(), (*h)->data);
    
    	// geht
    	// copy<vector<double>::const_iterator, double*>(v.begin(), v.end(), (*h)->data);
    
    	// geht
    	// Copy(v.begin(), v.end(), (*h)->data);
    
        return 0;
    }
    


  • Ok, ich sehe gerade, dass es nichts mit den Verschachtelungen zu tun hat, sondern mit dem Array mit der Grösse [1]. Scheint ihn irgendwie durcheinander zu bringen...


  • Administrator

    Ganz hässlich:

    Array* x = (Array*)malloc(sizeof(int) + sizeof(double) * 100);
    

    Ich bin mir nicht sicher, ob das funktioniert, so wie du es willst. Und in C++ ist es sowieso ein extrem Fehleranfälliger Stil. Wenn du überhaupt malloc benutzen möchtest, dann wenn schon eher noch:

    Array* x = (Array*)malloc(sizeof(Array) * 100);
    

    In C++ wäre es aber deutlich klüger und viel sicherer, wenn man es so macht:

    Array* x = new Array[100];
    
    // Mach was ...
    
    delete[] x;
    

    Wie auch immer, daran liegt das Problem nicht. Das Problem ist trivial!

    struct Array
    {
        int size;
        double data[1]; // da hat es Platz für EIN double Wert.
    };
    
    copy(v.begin(), v.end(), (*h)->data); // Du willst 100 Werte an eine Stelle speichern, wo nur einer hinpasst!
    

    Die STL, bzw. Std-Lib von Visual Studio ist so aufgebaut, dass sie das "nicht korrekte Schreiben" auf Speicher absichert und eine Exception wirft. Das macht die Std-Lib sicherer.
    Bei deiner Copy-Anweisung hast du das nicht und schreibst wild Werte an Stellen, welche dir zwar gehören, die Struktur aber so nicht vorgesehen hat.

    Die Fehlermeldung ist also absolut korrekt. Was du da machst, ist schlicht und einfach falsch!

    Grüssli



  • Array* x = (Array*)malloc(sizeof(Array) + sizeof(double) * 99);
    

    Array wird in der Regel Padding enthalten (int ist wahrscheinlich 4 Byte groß, double dagegen 8 byte und auch auf 8 Byte ausgerichtet). Abgesehen davon ist diese Überprüfung tatsächlich ein Bug der Standardbibliothek dieses Compilers (oder eher eine bewusste Abweichung von den Vorgaben des Standards, die für "normalen" Code durchaus zu begrüßen ist). Andererseits ist dieser struct-hack in C++ auch nicht unbedingt empfehlenswert.
    Etwas besser gekapselt:

    struct Array
    {
        union
        {
            int size;
            double align;
        };
        double* data() { return reinterpret_cast<double*>( this + 1 ); }
        const double* data() const { return reinterpret_cast<const double*>( this + 1 ); }
    };
    


  • Der struct-Hack ist leider vorgegeben, da LabView intern seine Arrays so speichert und ich zwischen diesem und einer DLL Daten austauschen möchte. Ich muss das ganze auch mit /Zp1 kompileren, damit er mir zwischen das int und das double nicht noch vier Bytes reinschiebt.
    Das "Problem" war hier ganz einfach, dass in diesem Fall die vermeintliche Zielgrösse bekannt ist, also 1, und der Algorithmus nicht weiss dass hinter dem 1. Element des Arrays noch Platz für 99 weiter ist. Wenn man das Ziel auf einen Pointer castet, verschwindet das Problem.
    Komisch finde ich nur, dass das Programm, in der ersten Variante, in der release-Version abstürzt.

    #include <algorithm>
    
    int main()
    {
    	using namespace std;
    
    	int a[100];
    
    	struct
    	{
    	  int y[1];
    	  int z[99];
    	} b;
    
    	// geht nicht
    	// copy(a, a + 100, b.y);
    
    	// geht
    	copy(a, a + 100, (int*)b.y);
    
        return 0;
    }
    

Anmelden zum Antworten