new[] und delete[] überladen



  • Hi, wie müsste denn der new[] bzw. delete[] operator überladen werden damit diese korrekt mit dynamischen Arrays umgehen? Siehe folgendes Minimalbeispiel:

    class Value
    {
    public:
    	Value() {};
    	Value(int i) {};
    
    	static void* operator new(size_t size);
    	static void* operator new[](size_t size);
    	static void operator delete(void *p);
    	static void operator delete[](void *p);
    };
    
    void* Value::operator new(size_t size)
    {
    	std::wcout << L"New: " << size << std::endl;
    	void* p = malloc(size);
    	if (p == 0)
    	{
    		throw std::bad_alloc();
    	}
    	return p;
    }
    
    void* Value::operator new[](size_t size)
    {
    	std::wcout << L"New[]: " << size << std::endl;
    	void* p = malloc(size);
    	if (p == 0)
    	{
    		throw std::bad_alloc();
    	}
    	return p;
    }
    
    void Value::operator delete(void *p)
    {
    	free(p);
    }
    
    void Value::operator delete[](void *p)
    {
    	free(p);
    }
    
    class MemoryTest
    {
    public:
    	MemoryTest() {};
    	MemoryTest(Value* value) {};
    };
    
    int main()
    {
    	std::wcout << L"Start:" << std::endl;
    	MemoryTest test = new Value[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    	std::wcout << L"End!" << std::endl;
    	std::cin.get();
    	return 0;
    }
    


  • Was ist denn nicht korrekt?



  • Na, wenn ich das richtig sehe wird bislang nur Speicher für ein Objekt reserviert. bei new[] habe ich aber ein Array also mehrere Elemente und brauche dann sicher auch entsprechend mehr Speicher. Gibt es eigentlich die Möglichkeit die Zahl der Elemente (size) an das MemoryTest-Objekt zu übergeben? Dieses soll wissen wie viele Elemente das Array hat.


  • Mod

    Enumerator schrieb:

    Na, wenn ich das richtig sehe wird bislang nur Speicher für ein Objekt reserviert. bei new[] habe ich aber ein Array also mehrere Elemente und brauche dann sicher auch entsprechend mehr Speicher. Gibt es eigentlich die Möglichkeit die Zahl der Elemente (size) an das MemoryTest-Objekt zu übergeben? Dieses soll wissen wie viele Elemente das Array hat.

    Was meinst du denn, was der Parameter des Operators macht? Mit dem von dir gewählten Bezeichner size hast du dich selber verwirrt. count wäre besser. Zumindest, wie hier, bei Überladungen auf Klassenebene. Bei einer globalen Überladung von new sähe das anders aus, aber da erübrigt sich dann auch die Frage nach der Anzahl der Objekte.

    PS: Das hier ist so übrigens nicht erlaubt und das sollte dir der Compiler auch sagen:

    new Value[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    

    Automatisches Bestimmen der Größe anhand der Initialisierungsliste gibt's nicht bei dynamischen Arrays. Du musst die Größe schon angeben:

    new Value[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    


  • Hi SeppJ, dann ist mein Compiler schlauer als deiner ;). Er gibt nicht mal eine Fehlermeldung oder Warnung aus. Allerdings hatte ich die Tage immer wieder interne Compilerfehler. Könnte sein, dass diese dadurch kamen. D. h. der Operator

    void* Class::operator new(size_t size)
    

    reserviert für ein Object den Speicher von der Größe size.
    Und der Operator

    void* Class::operator new[](size_t count)
    

    reserviert count mal den Speicher sizeof(Class)?


  • Mod

    Enumerator schrieb:

    Hi SeppJ, dann ist mein Compiler schlauer als deiner ;). Er gibt nicht mal eine Fehlermeldung oder Warnung aus. Allerdings hatte ich die Tage immer wieder interne Compilerfehler. Könnte sein, dass diese dadurch kamen.

    Welcher Compiler schluckt den Code?

    D. h. der Operator

    void* Class::operator new(size_t size)
    

    reserviert für ein Object den Speicher von der Größe size.
    Und der Operator

    void* Class::operator new[](size_t count)
    

    reserviert count mal den Speicher sizeof(Class)?

    Die reservieren gar nix. Das sind Funktionen, die du geschrieben hast und deren Aufruf dann an den Stellen erfolgt, wo new für deine Klasse benutzt wird. Bei der ersten Version wird der Compiler automatisch 1 für dein "size" übergeben (zumindest fällt mir gerade keine Möglichkeit ein, dass der Wert ein anderer sein könnte), beim zweiten eben die Anzahl der Elemente, die beim new[]-Aufruf angegeben wurde.
    Da du immer noch von Größen sprichst, wiederhole ich es noch einmal: Das sind keine Größenangaben, sondern Mengenangaben!

    Was gibt denn das Programm aus, das dein Wundercompiler da erzeugt hat? "New[]: 10"?



  • Enumerator schrieb:

    D. h. der Operator

    void* Class::operator new(size_t size)
    

    reserviert für ein Object den Speicher von der Größe size.
    Und der Operator

    void* Class::operator new[](size_t count)
    

    reserviert count mal den Speicher sizeof(Class)?

    SeppJ schrieb:

    ... Bei der ersten Version wird der Compiler automatisch 1 für dein "size" übergeben (zumindest fällt mir gerade keine Möglichkeit ein, dass der Wert ein anderer sein könnte), beim zweiten eben die Anzahl der Elemente, die beim new[]-Aufruf angegeben wurde.
    Da du immer noch von Größen sprichst, wiederhole ich es noch einmal: Das sind keine Größenangaben, sondern Mengenangaben! ...

    Das ist nicht korrekt. operator new/new[] erhält als Parameter immer die Anzahl der zu allozierenden Bytes.


  • Mod

    osdt schrieb:

    Enumerator schrieb:

    D. h. der Operator

    void* Class::operator new(size_t size)
    

    reserviert für ein Object den Speicher von der Größe size.
    Und der Operator

    void* Class::operator new[](size_t count)
    

    reserviert count mal den Speicher sizeof(Class)?

    SeppJ schrieb:

    ... Bei der ersten Version wird der Compiler automatisch 1 für dein "size" übergeben (zumindest fällt mir gerade keine Möglichkeit ein, dass der Wert ein anderer sein könnte), beim zweiten eben die Anzahl der Elemente, die beim new[]-Aufruf angegeben wurde.
    Da du immer noch von Größen sprichst, wiederhole ich es noch einmal: Das sind keine Größenangaben, sondern Mengenangaben! ...

    Das ist nicht korrekt. operator new/new[] erhält als Parameter immer die Anzahl der zu allozierenden Bytes.

    Stimmt, habe da was in der Dokumentation falsch gelesen. Ich muss daher meine obigen Tipps zurückziehen. Dann muss man eben die Anzahl der Elemente ausrechnen.


  • Mod

    SeppJ schrieb:

    Dann muss man eben die Anzahl der Elemente ausrechnen.

    Kannst du im Prinzip nicht. Der operator wird ja auch bei abgeleiteten Klassen verwendet, und das size-Argument dürfte i.d.R. auch Speicher für einen Zähler enthalten (zumindest, wenn das Objekt nicht trivial zu zerstören ist), damit der Compiler weiss, wieviele Objekte zu zerstören sind.



  • camper schrieb:

    Kannst du im Prinzip nicht. Der operator wird ja auch bei abgeleiteten Klassen verwendet, und das size-Argument dürfte i.d.R. auch Speicher für einen Zähler enthalten (zumindest, wenn das Objekt nicht trivial zu zerstören ist), damit der Compiler weiss, wieviele Objekte zu zerstören sind.

    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.


  • 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?


Anmelden zum Antworten