new[] und delete[] überladen



  • 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.~



  • [quote="osdt"]

    Arcoth schrieb:

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

    Weißt du solche Kommentare regen mich immer tierisch auf. Glaubst du ich mache das zum Spaß. Dann gefällt dir also die ver***** Lösung der Standard Library besser wo std::array<int, 5> ein anderer Typ als std::aray<int, 6> ist? Wie willst du denn so eine Funktion schreiben die ein Array unbestimmter Länge entgegennimmt? Ich denke die grauhaarigen Volldeppen im C++ Committe sollten mal das Design von C++ überdenken und aufhören diese Sprache noch weiter kaputtzufrickeln. Schau dir zum Beispiel mal an wie man Initializer-Listen umgesetzt hat. Furchtbar. Und das man im Jahr 2014 von einem dynamischen Array die Anzahl der Elemente nicht bestimmen kann ist ja wohl ein unding. Jetzt sagst du wahrscheinlich gleich wieder, aber das ist ein Parameter den der Compiler nicht braucht. Und ich (sowie diverse Studien) sage dir C++ ist bald eine Sprache die niemand mehr braucht.

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

    Der obige Programmcode funktioniert nun mit und ohne Initializer-Liste. Auch wenn ich diesen grundsätzlich für falsch halte. Denn ich schreibe die Größeninformation eine Position vor das Array, lese sie aber 2 Positionen davor wieder aus?

    Habe auch mal eine Testreihe gemacht:

    Pos. -3:	Pos. -2:	Pos. -1:	Code:
    
    Standard Operatoren:
    1	    	177    	    	4261281277	    int* arr = new int[10];
    1	    	177    	    	4261281277	    int* arr = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };				
    175    		4261281277	    10	    	    Base* arr = new Base[10];
    175	    	4261281277	    3452816845	    Base* arr = new Base[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    
    Überladene Operatoren (1x int reserviert):
    177    		4261281277	    3452816845 (40)	int* arr = new int[10];
    177    		4261281277	    3452816845 (40)	int* arr = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };				
    4261281277	3452816845 (84)	10 (10)	    	Base* arr = new Base[10];
    4261281277	3452816845 (84)	3452816845    	Base* arr = new Base[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    
    Überladene Operatoren (2x int reserviert):
    4261281277    	3452816845 (40)	3452816845	int* arr = new int[10];
    4261281277    	3452816845 (40)	3452816845	int* arr = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };				
    3452816845 (84) 3452816845	    10	    	Base* arr = new Base[10];
    3452816845 (84)	3452816845	    3452816845	Base* arr = new Base[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    

    Mal habe ich gar keinen zusätzlichen Speicher reserviert, dann zusätzlichen Speicher von sizeof(int) und noch einmal mit 2* sizeof(int). es scheint als könnte man an die Position direkt vor dem Array gar keinen eigenen Wert schreiben (siehe Werte in Klammern). Es sei denn man nimmt einen trivialen Typ wie int.



  • Mein letzter Satz ist quatsch. Man kann an die Position vor dem Array gar keinen Wert schreiben.


  • Mod

    Wie willst du denn so eine Funktion schreiben die ein Array unbestimmter Länge entgegennimmt?

    Falls du ein std::array unbestimmter Länge nehmen willst, bietet sich ein Funktionstemplate an

    template <std::size_t N>
    void f(std::array<int, N> const& arr)
    

    jedoch stellt sich gleichzeitig die Frage warum man nicht einfach mit Iteratoren arbeitet.

    Und das man im Jahr 2014 von einem dynamischen Array die Anzahl der Elemente nicht bestimmen kann ist ja wohl ein unding.

    Keiner verwendet rohe dynamische Arrays. Sie werden in Containern abgekapselt die wiederum Allokatoren verwenden. Und die Größe eines vector s zu bestimmen ist recht trivial.

    Denn ich schreibe die Größeninformation eine Position vor das Array, lese sie aber 2 Positionen davor wieder aus?

    Für Arrays deren Elemente zerstört werden müssen (sprich, für die ein Destruktor aufgerufen werden muss) wird der Compiler von dir ein wenig mehr Speicher verlangen als für das Array nötig. Er erhöht das Argument an operator new[] um sizeof(std::size_t) . Sagen wir bspw. du allozierst ein Array von std::string 's mit 10 Elementen, und die Gesamtgröße ist 160 Bytes. Der Compiler macht aus der Länge 168 (nehmen wir an dass wir auf einer 64-Bit Maschine sind). Du allozierst von malloc 176 Byte (noch einmal 8 drauf für dein size_t *), schreibst in die ersten 8 das Argument size , nämlich 168. Dann gibst du die restlichen 168 Bytes zurück. Der Compiler nimmt nun diesen Rückgabewert und schreibt in die ersten Acht Bytes die Länge (160) und überlässt dir dann die restlichen 160 für das Array-Objekt das er anschließend initialisiert.

    Edit: Und das Argument an operator delete[] wird er dann entsprechend um sizeof(std::size_t) korrigieren.

    * Dass du immer noch int verwendest...



  • Hi, das mit dem Template um die Funktion herum geht natürlich, aber genau das ist dieser Template-Fetischismus, den ich vermeiden möchte. Generell möchte ich alles von der Standard Library vermeiden, da C++ meiner Meinung nach mit dieser einen völlig falschen Weg geht. Alleine bei den Benennungen bekomme ich die Krise. Mich wundert ja, dass man Array "array" genannt hat. Ich weiß, ich bin in der Beziehung radikal, aber bislang habe ich immer bessere und vor allem für den Endanwender einfacher zu verwendende Lösungen gefunden. Auch ich verwende natürlich nicht die rohen Arrays, sondern kapsel diese in einer Array-Klasse. Diese ist nun aber von der Länge des Arrays unabhängig. Ein riesen Vorteil in meinen Augen.

    Eine Frage noch. Wie schreibe ich eine forward declaration für std::size_t?


  • Mod

    Generell möchte ich alles von der Standard Library vermeiden, da C++ meiner Meinung nach mit dieser einen völlig falschen Weg geht

    Das ist eine lächerliche Entscheidung mit der du nicht weit kommst. Das Iteratorenmodell ist beispiellos mächtig, abstrakt und effizient. Sicherlich gibt es an vielen Stellen Verbesserungspotenzial, das ist aber kein Grund die gesamte Standardbibliothek zu meiden.

    Wie schreibe ich eine forward declaration für std::size_t?

    Du kannst ein Typedef nicht vorwärtsdeklarieren. Binde doch einfach <cstddef> ein, der Header deklariert es nämlich.

    Hier nochmal ein Beispiel Programm:

    #include <new>
    #include <iostream>
    
    #include <cstdlib>
    
    #define dump(x) (std::cout << #x" = " << x << '\n') //inb4 mehr klammern
    
    void* operator new[](std::size_t size) throw(std::bad_alloc) // to shut up the warning
    {
    	auto base = static_cast<std::size_t*>(std::malloc(sizeof(std::size_t) + size));
    
    	dump(size);
    	dump(base);
    
    	if (!base)
    		throw std::bad_alloc();
    
    	*base = size;
    
    	return base + 1;
    }
    
    void operator delete[](void *p) noexcept // to shut up the warning
    {
    	std::cout << "freeing adress " << static_cast<std::size_t*>(p) - 1 << "\n\n";
    	free(static_cast<std::size_t*>(p) - 1);
    }
    
    template <typename T>
    std::size_t countof(T const* p)
    {
    	if (std::is_trivially_destructible<T>::value)
    		return *(reinterpret_cast<std::size_t const*>(p) - 1) / sizeof(T);
    
    	return (*(reinterpret_cast<std::size_t const*>(p) - 2) - sizeof(std::size_t)) / sizeof(T);
    }
    
    using arithmetic = int;
    struct trivial {};
    struct nontrivial { ~nontrivial() {std::cout << '~';} };
    
    int main()
    {
    	auto p1 = new arithmetic[10];
    	dump(p1);
    	dump(countof(p1));
    	delete [] p1;
    
    	auto p2 = new trivial[10];
    	dump(p2);
    	dump(countof(p2));
    	delete [] p2;
    
    	auto p3 = new nontrivial[10];
    	dump(p3);
    	dump(countof(p3));
    	delete [] p3;
    }
    

    Ausgabe auf meiner Maschine:

    size = 40
    base = 0x16e9010
    p1 = 0x16e9018
    countof(p1) = 10
    freeing adress 0x16e9010
    
    size = 10
    base = 0x16e9050
    p2 = 0x16e9058
    countof(p2) = 10
    freeing adress 0x16e9050
    
    size = 18
    base = 0x16e9070
    p3 = 0x16e9080
    countof(p3) = 10
    ~~~~~~~~~~freeing adress 0x16e9070
    


  • Enumerator schrieb:

    Auch ich verwende natürlich nicht die rohen Arrays, sondern kapsel diese in einer Array-Klasse. Diese ist nun aber von der Länge des Arrays unabhängig. Ein riesen Vorteil in meinen Augen.

    Oder ein Nachteil. Bei std::array werden die Elemente direkt auf dem Stack abgelegt. Alle anderen Arrayarten, also die aus anderen Sprachen sowie deine muessen erst zur Laufzeit auf dem Heap reserviert werden, was eine unnoetige Indirektion und damit reduzierte Laufzeit und nebenbei noch hoehere Speicherfragmentierung bedeutet.


Anmelden zum Antworten