new[] und delete[] überladen


  • Mod

    Marthog schrieb:

    Beim selbstueberladen muss man den Zaehler aber wahrscheinlich auch von Hand anlegen, also passt der Speicher schon so, man muss nur noch etwas mehr reservieren.

    Schon möglich, dass man selbst noch einen Eigenen raucht, aber der Compiler macht auf jeden Fall sein eigenes Ding: die Objekte werden ja kaputt gemacht, bevor die Deallokationsfunktion aufgerufen wird.



  • Hi, jetzt ist auch klar, warum ich nur interne Compiler-Fehler bekommen habe, obwohl ich beim Initialisieren mit Initializer-Liste keine Größe angegeben habe. Ist ein Bug in Visual Studio 2013 Update 1. Habe jetzt auf Update 3 geupdated und bekomme entsprechende Fehlermeldungen.

    Ich habe mal ein wenig mit folgendem Code experimentiert. Meine Idee ist den zu allozierenden Speicher um die größe eines int zu vergrößern und die Größeninformation vor dem Array zu speichern. Das funktioniert auch toll, nur der delete[] operator stürzt aus mir unerklärlichen Gründen ab. Vielleicht sieht von euch jemand warum? Noch besser wäre man könnte die Größe aus dem size extrahieren. Wäre für jeden Tipp dankbar. Warum ich das "-1" in der countof-Funktion brauche verstehe ich auch noch nicht.

    #include <memory>
    #include <iostream>
    #include <vector>
    #include <sstream>
    
    #define WIN32_OVERRIDE_ALLOCATORS
    
    static const size_t WORDSIZE = sizeof(int);
    
    class Base
    {
    public:
    	Base() : _value(0) {};
    	Base(const Base& base) : _value(base._value) {};
    	Base(int value) : _value(value) {};
    	virtual ~Base() {};
    
    	virtual void Test() {};
    
    	operator int()
    	{
    		return _value;
    	}
    
    private:
    	int _value;
    };
    
    void* operator new[](size_t size)
    {
    	std::wcout << L"operator new[]! ";
    	std::wcout << L"Size: " << size << std::endl;
    	int* tmp =  static_cast<int*>(malloc(WORDSIZE + size));
    	void* p =  static_cast<void*>(tmp + WORDSIZE);
    	if (p == 0)
    	{
    		throw std::bad_alloc();
    	}
    	std::wcout << L"tmp: " << tmp << std::endl;
    	std::wcout << L"p: " << p << std::endl;
    	*static_cast<int*>(tmp) = static_cast<int>(size);
    	return p;
    }
    
    void operator delete[](void *p)
    {
    	std::wcout << L"operator delete[]! " << std::endl;
    	std::wcout << L"p: " << p << std::endl;
    	void* tmp =  static_cast<int*>(p) - WORDSIZE;
    	std::wcout << L"tmp: " << tmp << std::endl;
    	free(tmp);
    }
    
    int countof(void* p)
    {
    	int* tmp = static_cast<int*>(p) - WORDSIZE - 1;
    	return *tmp;
    }
    
    int main()
    {
    	std::wcout << L"Start:" << std::endl;
    
    	std::wcout << L"WORDSIZE: " << WORDSIZE << std::endl;
    	std::wcout << L"Size of Base: " << sizeof(Base) << std::endl;
    
    	Base* arr = new Base[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    
    	std::wcout << L"Count of: " << countof(arr) << std::endl;
    
    	std::wcout << L"" << std::endl;
    	std::wcout << L"Element 0: " << arr[0] << std::endl;
    	std::wcout << L"Element 1: " << arr[1] << std::endl;
    	std::wcout << L"Element 2: " << arr[2] << std::endl;
    	std::wcout << L"Element 3: " << arr[3] << std::endl;
    	std::wcout << L"Element 4: " << arr[4] << std::endl;
    	std::wcout << L"Element 5: " << arr[5] << std::endl;
    	std::wcout << L"Element 6: " << arr[6] << std::endl;
    	std::wcout << L"Element 7: " << arr[7] << std::endl;
    	std::wcout << L"Element 8: " << arr[8] << std::endl;
    	std::wcout << L"Element 9: " << arr[9] << std::endl;
    	std::wcout << L"" << std::endl;
    
    	delete[] arr;
    
    	std::wcout << L"End!" << std::endl;
    	std::cin.get();
    	return 0;
    }
    

    Nebenbei bemerkt: Sobald ich in der Base-Klasse einen Destruktor definiere reserviert der new operator 4 zusätzlich Bytes (vtable vermutlich). Bei Datentypen wie int dagegen nicht. Lässt man die Initializer-Liste weg ist es sogar möglich die Größe des Arrays an der Position 1 Byte vor dem Array auszulesen. Mit aber leider nicht.


  • Mod

    Warum std:: ** w ** cout ?



  • @Arcoth Bitte nicht vom Thema ablenken. Übrigens hast du das in einem älteren Thread von mir schon mal gefragt.



  • Enumerator schrieb:

    Hi, jetzt ist auch klar, warum ich nur interne Compiler-Fehler bekommen habe, obwohl ich beim Initialisieren mit Initializer-Liste keine Größe angegeben habe. Ist ein Bug in Visual Studio 2013 Update 1. Habe jetzt auf Update 3 geupdated und bekomme entsprechende Fehlermeldungen.

    Ich habe mal ein wenig mit folgendem Code experimentiert. Meine Idee ist den zu allozierenden Speicher um die größe eines int zu vergrößern und die Größeninformation vor dem Array zu speichern. Das funktioniert auch toll, nur der delete[] operator stürzt aus mir unerklärlichen Gründen ab. Vielleicht sieht von euch jemand warum? Noch besser wäre man könnte die Größe aus dem size extrahieren. Wäre für jeden Tipp dankbar. Warum ich das "-1" in der countof-Funktion brauche verstehe ich auch noch nicht.

    #include <memory>
    #include <iostream>
    #include <vector>
    #include <sstream>
    
    #define WIN32_OVERRIDE_ALLOCATORS
    
    static const size_t WORDSIZE = sizeof(int);
    
    class Base
    {
    public:
    	Base() : _value(0) {};
    	Base(const Base& base) : _value(base._value) {};
    	Base(int value) : _value(value) {};
    	virtual ~Base() {};
    
    	virtual void Test() {};
    
    	operator int()
    	{
    		return _value;
    	}
    
    private:
    	int _value;
    };
    
    void* operator new[](size_t size)
    {
    	std::wcout << L"operator new[]! ";
    	std::wcout << L"Size: " << size << std::endl;
    	int* tmp =  static_cast<int*>(malloc(WORDSIZE + size));
    	void* p =  static_cast<void*>(tmp + WORDSIZE);                // <-- Pointer arithmetik nicht verstanden???
    	if (p == 0)
    	{
    		throw std::bad_alloc();
    	}
    	std::wcout << L"tmp: " << tmp << std::endl;
    	std::wcout << L"p: " << p << std::endl;
    	*static_cast<int*>(tmp) = static_cast<int>(size);
    	return p;
    }
    
    void operator delete[](void *p)
    {
    	std::wcout << L"operator delete[]! " << std::endl;
    	std::wcout << L"p: " << p << std::endl;
    	void* tmp =  static_cast<int*>(p) - WORDSIZE;                // <-- Pointer arithmetik nicht verstanden???
    	std::wcout << L"tmp: " << tmp << std::endl;
    	free(tmp);
    }
    
    int countof(void* p)
    {
    	int* tmp = static_cast<int*>(p) - WORDSIZE - 1;
    	return *tmp;
    }
    
    int main()
    {
    	std::wcout << L"Start:" << std::endl;
    
    	std::wcout << L"WORDSIZE: " << WORDSIZE << std::endl;
    	std::wcout << L"Size of Base: " << sizeof(Base) << std::endl;
    
    	Base* arr = new Base[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    
    	std::wcout << L"Count of: " << countof(arr) << std::endl;
    
    	std::wcout << L"" << std::endl;
    	std::wcout << L"Element 0: " << arr[0] << std::endl;
    	std::wcout << L"Element 1: " << arr[1] << std::endl;
    	std::wcout << L"Element 2: " << arr[2] << std::endl;
    	std::wcout << L"Element 3: " << arr[3] << std::endl;
    	std::wcout << L"Element 4: " << arr[4] << std::endl;
    	std::wcout << L"Element 5: " << arr[5] << std::endl;
    	std::wcout << L"Element 6: " << arr[6] << std::endl;
    	std::wcout << L"Element 7: " << arr[7] << std::endl;
    	std::wcout << L"Element 8: " << arr[8] << std::endl;
    	std::wcout << L"Element 9: " << arr[9] << std::endl;
    	std::wcout << L"" << std::endl;
    
    	delete[] arr;
    
    	std::wcout << L"End!" << std::endl;
    	std::cin.get();
    	return 0;
    }
    

    Nebenbei bemerkt: Sobald ich in der Base-Klasse einen Destruktor definiere reserviert der new operator 4 zusätzlich Bytes (vtable vermutlich). Bei Datentypen wie int dagegen nicht. Lässt man die Initializer-Liste weg ist es sogar möglich die Größe des Arrays an der Position 1 Byte vor dem Array auszulesen. Mit aber leider nicht.

    siehe mein Kommentar


  • Mod

    Enumerator schrieb:

    Bitte nicht vom Thema ablenken.

    Ich lenke von gar nichts ab, sondern stelle nebenläufig eine Frage.

    void* p =  static_cast<void*>(tmp + WORDSIZE);
        if (p == 0)
        {
            throw std::bad_alloc();
        }
    

    Du wolltest wohl testen ob tmp gleich Null ist, bei p kann das nämlich nicht der Fall sein.

    int* tmp =  static_cast<int*>(malloc(WORDSIZE + size));
        void* p =  static_cast<void*>(tmp + WORDSIZE);
    

    tmp + WORDSIZE erhöht die Adresse in tmp um sizeof(int)*WORDSIZE , nicht WORDSIZE . Das trifft auch auf einige andere Stellen in deinem Code zu, die Zeigerarithmetik stimmt dort einfach nicht.

    Ebenfalls falsch ist es die Größe in int abzuspeichern. Dafür nimmt man std::size_t , wie auch die Parameter.

    Korrigiert sieht dein Code so aus

    void* operator new[](std::size_t size)
    {
    	std::size_t* base = static_cast<std::size_t*>(std::malloc(sizeof(std::size_t) + size));
    
    	if (!base)
    		throw std::bad_alloc();
    
    	*base = size;
    
    	return base + 1;
    }
    
    void operator delete[](void *p)
    {
    	free(static_cast<std::size_t*>(p) - 1);
    }
    
    std::size_t countof(void* p)
    {
    	return *(static_cast<std::size_t*>(p) - 1);
    }
    


  • Hi, ich habe das mehr oder weniger von hier übernommen:
    http://www.parashift.com/c++-faq-lite/num-elems-in-new-array-overalloc.html
    Aber dann wäre es nett wenn du wenigstens noch sagst wie es denn richtig geht. Euch muss man mal wieder alles aus der Nase ziehen ;).



  • Ah, Arcoth war schneller. Super werde das morgen probieren. Danke schonmal.


  • Mod

    Dir hätte doch auffallen müssen dass die Adressen in p und tmp sich um 16 unterscheiden? Bei mir sah die Ausgabe so aus:

    tmp: 0x215f010
    p: 0x215f020
    

    Da Adressen in hexadezimaler Schreibweise ausgegeben werden gibt die zweite Ziffer von Rechts Vielfache von 16 an, p zeigt also 16 Adressen weiter als tmp . Es hätte aber eine Differenz von 4 geben müssen (für size_t ggf. 8).



  • Ok, funktioniert, nur die countof Funktion gibt für den Fall mit Initializer-Liste Schrott aus:

    Base* arr = new Base[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    

    So stimmts:

    Base* arr = new Base[10];
    

    Woran liegt das?



  • Ich hatte nur die Adressen vom new und delete verglichen. Und die haben zueinander gepasst ;).


  • Mod

    Ok, funktioniert, nur die countof Funktion gibt für den Fall mit Initializer-Liste Schrott aus:

    Das ist äußerst merkwürdig. Die Initializer-Liste gibt lediglich die Werte an mit denen die Objekte initialisiert werden, es sollte für die Adressen völlig irrelevant sein ob oder was man da angibt. Ganz sicher dass der Fehler nicht bei dir liegt? Es kann selbstverständlich auch ein VC++-Bug sein. Ich kann den Fehler jedenfalls nicht reproduzieren, bei mir klappt es sowohl mit als auch ohne.



  • Enumerator schrieb:

    Hi, ich habe das mehr oder weniger von hier übernommen:
    http://www.parashift.com/c++-faq-lite/num-elems-in-new-array-overalloc.html
    Aber dann wäre es nett wenn du wenigstens noch sagst wie es denn richtig geht. Euch muss man mal wieder alles aus der Nase ziehen ;).

    Ich habe zwar noch nie ein C++ Anfängerbuch in der Hand gehabt, aber ich gehe mal davon aus, daß in jeden besseren mindestens ein (Unter)kapitel Pointerarithmetik exisitiert.

    Das solltest Du lesen und verstehen. Dann hättest Du auch Dein Problem selbst lösen können. Dann ist der Lerneffekt größer. Arcoth hat es sicherlich gut gemeint, aber didaktisch ist es meiner Meinung nach nicht optimal.

    mfg Martin



  • Arcoth schrieb:

    Ok, funktioniert, nur die countof Funktion gibt für den Fall mit Initializer-Liste Schrott aus:

    Das ist äußerst merkwürdig. Die Initializer-Liste gibt lediglich die Werte an mit denen die Objekte initialisiert werden, es sollte für die Adressen völlig irrelevant sein ob oder was man da angibt. Ganz sicher dass der Fehler nicht bei dir liegt? Es kann selbstverständlich auch ein VC++-Bug sein. Ich kann den Fehler jedenfalls nicht reproduzieren, bei mir klappt es sowohl mit als auch ohne.

    Scheint mir auch ein weiterer Bug in Visual Studio zu sein. Bei Ideone läuft es nämlich ebenfalls korrekt. Schon ein Ding was Microsoft da mit den Initializer-Listen hingefrickelt hat. Würde mich mal interessieren, ob das Problem mit der countof-Funktion noch jemand bestätigen kann (Visual Studio 2013 Update 3).



  • Arcoth schrieb:

    ... Korrigiert sieht dein Code so aus

    void* operator new[](std::size_t size)
    {
    	std::size_t* base = static_cast<std::size_t*>(std::malloc(sizeof(std::size_t) + size));
    	
    	if (!base)
    		throw std::bad_alloc();
    
    	*base = size;
    	
    	return base + 1;
    }
    
    void operator delete[](void *p)
    {
    	free(static_cast<std::size_t*>(p) - 1);
    }
    
    std::size_t countof(void* p)
    {
    	return *(static_cast<std::size_t*>(p) - 1);
    }
    

    Wenn es sich um eine Klasse mit nicht-trivialem Destruktor handelt, liefert die Funktion countof(p) die vom Compiler gespeicherte [1] Anzahl Elemente, also nicht den in Zeile 8 gespeicherten Parameter size (Anzahl zu reservierender Bytes). Nur deshalb liefert die Funktion das 'erwartete' Ergebnis, selbst ohne Überladung der Operatoren.

    [1] für Klassen mit nicht-trivialem Destruktor muss der Compiler die Anzahl Elemente speichern, um vor dem Aufruf von ::operator delete[](p) für jedes Element den Destruktor aufrufen zu können. Hierfür reserviert der Compiler (g++) sizeof(size_t) Bytes zusätzlich am Anfang des von ::operator new[](n) gelieferten Speichers.


  • Mod

    @osdt: Ja, das habe ich nicht berücksichtigt. Der Compiler erhöht das Argument und den Rückgabewert um Platz für zusätzliche Information wie die Länge zu schaffen, wie von camper und auch hier vom Standard erwähnt:

    Footnote 226 schrieb:

    It is not the direct responsibility of operator new[](std::size_t) or operator delete[](void*) to note the repetition count or element size of the array. Those operations are performed elsewhere in the array new and delete expressions. The array new expression, may, however, increase the size argument to operator new[](std::size_t) to obtain space to store supplemental information.

    Modifiziert man die countof -Funktion entsprechend bekommt man auch die selbst gespeicherte Länge:

    std::size_t countof(void* p)
    {
        return *(static_cast<std::size_t*>(p) - 2);
    }
    

    Das ist selbstverständlich nicht portabel.



  • Arcoth schrieb:

    Modifiziert man die countof -Funktion entsprechend bekommt man auch die selbst gespeicherte Länge:

    std::size_t countof(void* p)
    {
        return *(static_cast<std::size_t*>(p) - 2);
    }
    

    Das ist selbstverständlich nicht portabel.

    Länge meint hier die Anzahl Bytes, nicht die Anzahl Elemente. Eine generalisierte Funktion - vorausgesetzt die globalen Operatoren sind wie oben überladen - könnte wie folgt aussehen (ungetestet):

    #include <type_traits>
    
    template <typename T>
    std::size_t countof(T* p)
    {
        if (std::is_trivially_destructible<T>::value)
            return *(reinterpret_cast<std::size_t*>(p) - 1) / sizeof(T);
        else
            return (*(reinterpret_cast<std::size_t*>(p) - 2) - sizeof(size_t)) / sizeof(T);
    }
    

    Das sollte dann sogar für 'int* p = new int[10]' funktionieren, selbstverständlich auch nicht portabel.


  • Mod

    Länge meint hier die Anzahl Bytes, nicht die Anzahl Elemente.

    Ja, bei alldem hast du natürlich Recht. Hätte so spät keinen Unsinn verzapfen sollen 😃

    std::is_trivially_destructible<T>::value
    

    ist kein ausreichendes Kriterium. Selbst bei Klassen mit trivialem Destruktor speichert der Compiler (zumindest Clang und GCC) selbst die Länge ab. std::is_class dürfte es besser treffen, ist wahrscheinlich aber auch nicht Compiler übergreifend.



  • Arcoth schrieb:

    std::is_trivially_destructible<T>::value
    

    ist kein ausreichendes Kriterium. Selbst bei Klassen mit trivialem Destruktor speichert der Compiler (zumindest Clang und GCC) selbst die Länge ab. std::is_class dürfte es besser treffen, ist wahrscheinlich aber auch nicht Compiler übergreifend.

    Es würde bedeuten, dass der Compiler einen Wert speichert ohne ihn zu benötigen. GCC ist anscheinend cleverer http://ideone.com/MNpLf4 .
    Aber du hast schon Recht, es ist compilerabhängig und könnte sich schon mit der nächsten Version ändern.

    @TE: wer glaubt so einen Hack zu benötigen sollte sein Design nochmal überdenken 😉


  • Mod

    Komischerweise gab es wohl einen Fehler mit CodeBlocks, mein Testprogramm wurde gar nicht kompiliert und das bereits vorhandene Kompilat ausgeführt*... den Rest kann man sich vorstellen. 🙄 *Schäm*
    Natürlich speichern weder Clang noch GCC die Größe wenn sie nicht benötigt wird, hat mich auch stark gewundert.

    ~* Bei so etwas wird nur etwas in den Log geschrieben, den ich meistens gar nicht offen habe.~


Anmelden zum Antworten