new[] und delete[] überladen



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



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


Anmelden zum Antworten