wie weiss delete[] array die Array Grösse



  • An der Stelle, wird er, bevor er die deallocation function aufruft, die Menge der Element da nachschlagen, wo er sie vorher abgelegt hat. Ok, dass ist an der Stelle jetzt nicht mehr der Compiler, sonder die Runtime. Worum geht es Dir bei Deiner Frage?



  • dove schrieb:

    ja, das ist ein fehler im design von C++.

    < C++14

    http://http://en.cppreference.com/w/cpp/memory/new/operator_delete

    void operator delete ( void* ptr, std::size_t sz ); (5) (since C++14)
    void operator delete[]( void* ptr, std::size_t sz ); (6) (since C++14)



  • Es ist mehr noch ein Fehler von C gewesen, wie Alexandrescu angemerkt hat:

    struct Blk {
        void* ptr;
        size_t size;
    }
    
    Blk malloc(int);
    void free(Blk);
    


  • ja, an alexandrescu habe ich auch gedacht. v.a. sein vortrag darüber, wie er (heutzutage) allokatoren designen würde. tja, und dann schaut man in die standard-pläne und findet so etwas wie polymorphic_allocator. zu wenig, zu langsam, zu umständlich...

    danke manni66 für den hinweis.



  • Torsten Robitzki schrieb:

    Du müsstest die Größe ja nicht mit angeben, dass kann der compiler machen, den der kennt bei einem delete die Größe des Objekts. Mit der Information kann man einen allocator effektiver implementieren.

    Wie wird das begründet? Was ist der Nachteil dabei, die Länge des allozierten Speicherblocks in ptr[-irgendwas] unterzubringen?



  • Andromeda schrieb:

    Torsten Robitzki schrieb:

    Du müsstest die Größe ja nicht mit angeben, dass kann der compiler machen, den der kennt bei einem delete die Größe des Objekts. Mit der Information kann man einen allocator effektiver implementieren.

    Wie wird das begründet? Was ist der Nachteil dabei, die Länge des allozierten Speicherblocks in ptr[-irgendwas] unterzubringen?

    Du must zur Laufzeit etwas ablegen, was in der Regel zur compiler-zeit bekannt ist. Und Du must etwas mehr Speicher aufwenden, als nötig ist, um die Information abzulegen.

    Meist sind Allokatoren so aufgebaut, dass sie für kleinere, Speichergrößen einen eigenen Allocator haben. Aus einem `delete t` könnte also bei zur compilezeit bekanntem sizeof(*t) das direkte Einhängen des Zeigers in eine freelist für diese Länge werden. Das wären dann zwei Zuweisungen.



  • Der genannte Vortrag ist dieser hier:
    https://www.youtube.com/watch?v=LIb3L4vKZ7U

    Ist zu empfehlen.



  • Torsten Robitzki schrieb:

    Andromeda schrieb:

    Torsten Robitzki schrieb:

    Du müsstest die Größe ja nicht mit angeben, dass kann der compiler machen, den der kennt bei einem delete die Größe des Objekts. Mit der Information kann man einen allocator effektiver implementieren.

    Wie wird das begründet? Was ist der Nachteil dabei, die Länge des allozierten Speicherblocks in ptr[-irgendwas] unterzubringen?

    Du must zur Laufzeit etwas ablegen, was in der Regel zur compiler-zeit bekannt ist. Und Du must etwas mehr Speicher aufwenden, als nötig ist, um die Information abzulegen.

    Okay, aber die Längeninformation zu sparen, halte ich für Mikrooptimierung. Meiner Meinung nach, ist das in den meisten Fällen total sinnfrei. Na ja, aber wer es braucht ...

    Torsten Robitzki schrieb:

    Meist sind Allokatoren so aufgebaut, dass sie für kleinere, Speichergrößen einen eigenen Allocator haben. Aus einem `delete t` könnte also bei zur compilezeit bekanntem sizeof(*t) das direkte Einhängen des Zeigers in eine freelist für diese Länge werden. Das wären dann zwei Zuweisungen.

    Ich sehe es schon kommen: irgendwann hat C++ auch einen schicken, standardmäßigen Garbage Collector. 🙂



  • Andromeda schrieb:

    Okay, aber die Längeninformation zu sparen, halte ich für Mikrooptimierung. Meiner Meinung nach, ist das in den meisten Fällen total sinnfrei. Na ja, aber wer es braucht ...

    Naja, wenn Du sehr viele kleine Objekte hast, deren dynamisch alloziierter Speicher nicht viel größer als sizeof(void*) ist, dann fällt das schon ins Gewicht. Entscheidend ist aber, dass wenn die Größe zur Compiler-Zeit bekannt ist, ich evtl. schon einige Schritte bei der Speicherverwaltung sparen kann. Und C++ ist halt um "You pay only for what you use..." (oder so ähnlich) gebaut 😉



  • Torsten Robitzki schrieb:

    Naja, wenn Du sehr viele kleine Objekte hast, deren dynamisch alloziierter Speicher nicht viel größer als sizeof(void*) ist, dann fällt das schon ins Gewicht.

    Ja, durchaus.

    Torsten Robitzki schrieb:

    Entscheidend ist aber, dass wenn die Größe zur Compiler-Zeit bekannt ist, ich evtl. schon einige Schritte bei der Speicherverwaltung sparen kann.

    Da kann ich dir jetzt nicht ganz folgen. Dass der Allokator die Größe nur intern kennt und man sie nicht abfragen kann, halte ich auch für eine Design Schwäche. Mit dem Vorschlag von Alexandrescu kann ich auch durchaus was anfangen. Da wird die Größe aber auch zur Laufzeit mitverwaltet. Aber wie willst du das einsparen und nur vom Compiler erledigen lassen, wenn du im Programm Zeiger rumreichst, die nicht die Größeninformationen mitschleppen?



  • Mechanics schrieb:

    Aber wie willst du das einsparen und nur vom Compiler erledigen lassen, wenn du im Programm Zeiger rumreichst, die nicht die Größeninformationen mitschleppen?

    Wenn wir vom nicht array-Fall ausgehen, dann steckt die Größeninformation im Typen für den Fall dass der statische Typ dem dynamischen Typ entspricht (also kein delete über einen Zeiger auf eine Basis mit virtuellem Destruktor).

    Für arrays muss die Größe des Arrays zur Laufzeit mit abgelegt werden, weil sich die nicht aus dem Typen ableiten lässt.

    Da haben wir wohl etwas aneinander vorbei geredet, sorry.

    Typen wie std::vector<>, die wahrscheinlich die Hauptnutzer von dynamisch alloziierten arrays sind, kennen die Größe des alloziierten Speichers sogar nochmal explizit, sodass der allocator die Information an der Stelle auch nicht mehr braucht.



  • Torsten Robitzki schrieb:

    Naja, wenn Du sehr viele kleine Objekte hast, deren dynamisch alloziierter Speicher nicht viel größer als sizeof(void*) ist, dann fällt das schon ins Gewicht.

    Sicherlich; je höher die Frequenz von new/delete, desto störender macht sich jeder einzelne Taktzyklus dieser Funktionen bemerkbar. Wer aber auf solche Probleme stößt, der sollte vielleicht bessser mal seinen Programmierstil überprüfen, als auf die Heap-Verwaltung zu schimpfen.

    Torsten Robitzki schrieb:

    Entscheidend ist aber, dass wenn die Größe zur Compiler-Zeit bekannt ist, ich evtl. schon einige Schritte bei der Speicherverwaltung sparen kann.

    Ja, klar. Ein schlauer Compiler wird womöglich für sowas gar nicht erst den Heap benutzen, sondern solche kurzlebigen Miniobjekte im Stackframe speichern, falls sie funktionslokal bleiben, oder gar eine eigene Speicherverwaltung dafür mitbringen.



  • dove schrieb:

    ja, das ist ein fehler im design von C++.
    dem new sagt man die größe, also sollte man dem delete auch die größe sagen (müssen).

    Was ist dein Argument dafür? Symmetrie? Kann ich nicht durchgehen lassen, ist beim Programmieren oft einfach keine gute Idee alles auf Teufel-komm-raus symmetrischen machen zu wollen.
    Mal ganz davon abgesehen dass oft nicht klar ist was "symmetrisch" überhaupt bedeuten soll.

    Man könnte genau so sagen symmetrisch wäre es, wenn man bei new die Grösse angibt und nen Zeiger zurückbekommt, und bei delete den Zeiger angibt und die Grösse zurück bekommt. Wäre aber genau so beknackt (noch beknackter).

    Und...
    Wenn es deiner Meinung gut/korrekt/... wäre dass man bei delete die Grösse mitgeben muss...
    Was ist dann mit fclose()? Sollte man da den Filenamen nochmal angeben müssen? -> Blödsinn.
    Bzw. mit jedem Destruktor eines Objekts dessen Konstruktor man mit bestimmten Parametern aufgerufen hat?

    Nene.
    Der Designfehler ist höchstens dass man die Grösse nicht abfragen kann. Weil die Implementierung sich die Grösse sowieso merken muss (zumindest bei Arrays aus Objekten mit nicht-trivialem Dtor).



  • hustbaer schrieb:

    Nene.
    Der Designfehler ist höchstens dass man die Grösse nicht abfragen kann.

    Das kannst du doch spielend leicht umgehen, indem du sie selber speicherst.
    Jetzt wird es aber albern. 🙄



  • Jodocus hat es gleich erwähnt. Richtig designt wäre es so:

    Jodocus schrieb:

    Es ist mehr noch ein Fehler von C gewesen, wie Alexandrescu angemerkt hat:

    struct Blk {
        void* ptr;
        size_t size;
    }
    
    Blk malloc(int);
    void free(Blk);
    


  • @Andromeda
    Natürlich kann man es leicht umgehen. Aber es verschwendet Speicher. Unnötiger Overhead der gerade bei vielen kleinen Allokationen leicht relevante Dimensionen annehmen kann.

    Machst du eigentlich noch was anderes als mit aus Prinzip zu "kritisieren", egal wie sinnvoll oder sinnlos die "Kritik" fachlich ist?



  • Aber ist es nicht so dass die tatsächlich allozierte Größe eh Implmentierungsabhängig ist (also dass auch mehr Speicher reserviert werden kann als angefordert)?

    Dann wäre es wieder relativ sinnlos auf die Größe zugreifen zu können, weil man sich ja nicht darauf verlassen kann dass diese auch der Größe des arrays entspricht.



  • happystudent schrieb:

    Aber ist es nicht so dass die tatsächlich allozierte Größe eh Implmentierungsabhängig ist (also dass auch mehr Speicher reserviert werden kann als angefordert)?

    Ja, klar. Üblicherweise ist der zurückgebene Pointer Teil eines ListElements des Allokators und außerdem kann es durchaus sein, dass deine angefordete Speichergröße aufgerundet wird, weil der Allokator eine bestimmte Granularität voraussetzt, um Fragmentierung zu minimieren.

    happystudent schrieb:

    Dann wäre es wieder relativ sinnlos auf die Größe zugreifen zu können, weil man sich ja nicht darauf verlassen kann dass diese auch der Größe des arrays entspricht.

    Nicht zwangsläufig, weil der Allokator das ja verrechnen kann. Zugegeben: das ist wieder ein Sonderfall, wie das vielzitierte hochfrequente allozieren und freigeben kleiner Objekte. Aber manche lieben sowas ja. 🙂



  • Andromeda schrieb:

    Aber manche lieben sowas ja. 🙂

    Das glaube ich nicht. Es gibt welche, die haben keine Ahnung von sowas und deswegen ist es denen egal (aber sie bevorzugen dann diesen Stil, ja). Aber es gibt auch viele Fälle, da kommst nicht drumherum. Allein schon type erasure wie bei std::function oder tausende andere Fälle, wo man das einfach braucht oder die Alternativen andere Nachteile haben.



  • @happystudent
    Wenn das Array aus Elementen besteht deren Destruktor nicht trivial ist, dann muss die exakte Grösse irgendwo gespeichert sein. Sonst wüsste die Implementierung ja nicht wie viele Objekte bei delete[] zerstört werden müssen bevor der Speicher freigegeben werden kann.
    Man könnte den Support für "Grösse Abfragen" auch einfach auf solche Arrays beschränken.

    ps: Es gibt nämlich Allokatoren die Pools für kleine Allokationen verwenden, wo mehr als nur ein wenig aufgerundet werden muss. Also angenommen 160 Byte werden für 10 Objekte a 16 Byte angefordert, aber der kleinste passende Pool verwaltet 180 Byte Blöcke. Das wären dann 11 Objekte wenn man zurückrechnet. Bei Arrays aus Objekten mit trivialem Destruktor wäre das egal, es muss ja nur der Speicher freigegeben werden. Und das kann der Allokator, ohne dass er sich irgendwo pro Block merkt wie gross dieser war, so lange der Block aus einem seiner fixgrösse Pools stammt. (Man kann das so implementieren dass man anhand der Adresse feststellen kann aus welchem Pool ein Block kommt. Und damit kennt man dann die Grösse des Pool-Blocks, also die 180 Byte. Nicht aber die 160.)

    Wenn der Destruktor aber aufgerufen werden muss, dann muss die Implementierung halt doch wissen dass es bloss 10 Objekte sind.
    D.h. wenn die Implementierung nicht an einen Allokator gebunden ist (=mit beliebigen Allokatoren klar kommen will/muss), dann muss sie - egal was der Allokator macht - die Grösse in diesem non-trivial-dtor Fall selbst nochmal irgendwo abspeichern.

    Im Worst Case wären wird dann bei drei Kopien der Grösse:
    1x im bzw. für den Allokator selbst
    1x irgendwo für die C++ Implementierung
    1x im eigenen Programm

    Sogesehen halte ich es schon für eine "ungünstige" Entscheidung dass man die Array-Grösse grundsätzlich nicht abfragen kann. Grundsätzlich immer wäre vermutlich auch verkehrt, weil man dadurch bei Allokatoren wie oben beschrieben und trivial-dtor Objekten wieder eine u.U. unnötige Kopie der Grösse erzwingen würde.


Anmelden zum Antworten