delete [] p ?



  • [][][] schrieb:

    aber wenn delete auf ein mit new[] erzeugtes array sowieso undefiniert ist, warum muss man denn dann explizit delete[] aufrufen?

    Weil der Compiler das an gewissen Stellen ja gar nicht wissen kann, ob der übergebene Zeiger auf ein Array zeigt oder auf ein einzelnes Objekt:

    void MyPersonalDeleteFunction( int* p )
    {
        delete p;
        // oder
        delete[] p;
        // ?
    }
    
    ..
    int* a = new int;
    int* b = new int[100];
    MyPersonalDeleteFunction( a );
    MyPersonalDeleteFunction( b );
    

    [][][] schrieb:

    wenn der compiler bei delete[] weiß, wieviele felder freizugeben sind, müsste er es doch auch bei delete wissen können.

    Da ist was wahres dran.. Allerdings wissen die Compiler oft mehr, als was der Standard vorgibt zu wissen.



  • Nun, er kompiliert und meckert nicht, wenn er hier ist...

    Person *p2 = new Person(name2, age);
    p2->print();
    delete [] p2;
    


  • Seikilos schrieb:

    Nun, er kompiliert und meckert nicht, wenn er hier ist...

    Das muss er auch nicht. Er könnte auch einen Sack Reis in Korea sprengen. Es ist und bleibt aber undefiniert, sprich illegal.



  • Ich freu mich schon drauf, dem Lehrer auf die Frage antworten zu können, dass das ein Fehler ist... 😃



  • [][][] schrieb:

    wenn der compiler bei delete[] weiß, wieviele felder freizugeben sind, müsste er es doch auch bei delete wissen können.

    Die Verwaltung der Arraylänge kostet zusätzlichen Aufwand, den kann sich der Compiler sparen, wenn er "weiß", daß er mit Einzelobjekten hantiert.

    Eine Beispiel-Implementierung (Semi-Pseudocode):

    //op new - liefert Speicher für ein Objekt (size Bytes)
    void* operator new(size_t size)
    {
      return malloc(size);
    }
    void operator delete(void* p)
    {
      free(p);
    }
    
    //op new[] - liefert Speicher für ein Array (size Bytes gesamt)
    void* operator new[](size_t size)
    {
      char* ptr = malloc(size+sizeof(size_t));
      *((int*)ptr) = size;
      return ptr+sizeof(size_t);
    }
    void operator delete[](void*p)
    {
      free((char*)p-sizeof(size_t));
    }
    


  • CStoll schrieb:

    Die Verwaltung der Arraylänge kostet zusätzlichen Aufwand, den kann sich der Compiler sparen, wenn er "weiß", daß er mit Einzelobjekten hantiert.

    dein beispiel zeigt aber, wie gering der zusätzliche aufwand wäre - im vergleich zu dem gewinn an konsistenz und dem verschwinden einer bösen potenziellen fehlerquelle, wenn delete[] wegfiele.

    Badestrand schrieb:

    Weil der Compiler das an gewissen Stellen ja gar nicht wissen kann, ob der übergebene Zeiger auf ein Array zeigt oder auf ein einzelnes Objekt:

    wenn er über die größe des speicherbereichs buch führen kann, könnte er doch genauso gut auch über die objektanzahl buch führen?



  • [][][] schrieb:

    wenn er über die größe des speicherbereichs buch führen kann, könnte er doch genauso gut auch über die objektanzahl buch führen?

    Der, der über die Größe des Speichers Buch führt, ist aber möglicherweise (bzw. meistens) jemand anderes, als derjenige, der sich um die Destruktion kümmert. Wenn z.B. die Operatoren new und delete auf malloc und free zurückgreifen, ist ihnen die Größe des Speichers egal, darum kümmern sich malloc/free. Die Destruktion von Objekten muss der C++-Teil übernehmen, dafür ist aber nur die Anzahl erforderlich. (Wenngleich man natürlich zwischen Anzahl und Größe umrechnen kann).



  • [][][] schrieb:

    CStoll schrieb:

    Die Verwaltung der Arraylänge kostet zusätzlichen Aufwand, den kann sich der Compiler sparen, wenn er "weiß", daß er mit Einzelobjekten hantiert.

    dein beispiel zeigt aber, wie gering der zusätzliche aufwand wäre - im vergleich zu dem gewinn an konsistenz und dem verschwinden einer bösen potenziellen fehlerquelle, wenn delete[] wegfiele.

    Es gibt verschiedene Möglichkeiten, die Größe des Arrays zu verwalten (u.a. auch über eine map<void*,size_t> oder per Zugriff auf die Verwaltungsdaten von malloc/free) - die jeweils ihre eigenen Vor- und Nachteile haben. Auf jeden Fall muß new[] diese Anzahl irgendwo hinterlegen und das kostet Platz* (und Rechenzeit).

    * Bei kleinen Objekten kann es durchaus vorkommen, daß du damit deinen speicherbedarf verdoppelst.



  • LordJaxom schrieb:

    Der, der über die Größe des Speichers Buch führt, ist aber möglicherweise (bzw. meistens) jemand anderes, als derjenige, der sich um die Destruktion kümmert. Wenn z.B. die Operatoren new und delete auf malloc und free zurückgreifen, ist ihnen die Größe des Speichers egal, darum kümmern sich malloc/free. Die Destruktion von Objekten muss der C++-Teil übernehmen, dafür ist aber nur die Anzahl erforderlich. (Wenngleich man natürlich zwischen Anzahl und Größe umrechnen kann).

    das überzeugt mich nicht. delete kennt die größe des speicherbereichs. delete[] kennt außerdem die anzahl der zu zerstörenden objekte. das heißt, dass beide informationen irgendwo abgelegt sein müssen, und der zugrunde liegende code weiß, wie er an diese informationen heran kommt.
    wenn er diese zwei size_t-variablen mit jedem new[] ablegen kann, warum dann nicht noch ein zusätzliches bit, um automatisch zwischen new und new[] zu unterscheiden?

    mal ne andere frage: wenn ich mein eigenes delete schreiben würde, könnte ich es theoretisch so schreiben, dass es ein notwendiges delete[] selbst erkennen und durchführen kann?



  • new/delete braucht die Information nicht, wieviele Elemente das Array nun hat (es ist immer genau 1), also wozu sollte es diese Information noch speichern? Das kostet nur Speicherplatz und Rechenzeit.

    PS: Wie du deine eigenen new/delete's implementierst, ist natürlich dir selber überlassen - der ANSI-Standard legt nur fest, daß es undefiniert ist, wenn du Array- und Single-Operatoren mischst.



  • CStoll schrieb:

    new/delete braucht die Information nicht, wieviele Elemente das Array nun hat (es ist immer genau 1), also wozu sollte es diese Information noch speichern? Das kostet nur Speicherplatz und Rechenzeit.

    es sollte diese information speichern, weil eine unterscheidung zwischen delete und delete[] fehleranfällig ist und weil es die syntax komplizierter als notwendig macht.

    die erhöhte rechenzeit zur abfrage eines bits halte ich für kaum relevant, wenn man den üblichen aufwand für eine (de-)allokation in relation dazu setzt.
    der speicherbedarf stiege in der tat an, günstigenfalls aber nur um ein bit (nicht gesetzt = nur ein element), das sich auf vielen plattformen bestimmt noch in den ohnehin vorhandenen verwaltungsinformationen unterbringen ließe.

    und für sehr kleine allokationen könnte man ja immer noch in eigener verantwortung auf malloc und free zurückgreifen.

    gibt es wirklich keine besseren gründe für eine unterscheidung außer den genannten?

    CStoll schrieb:

    der ANSI-Standard legt nur fest, daß es undefiniert ist, wenn du Array- und Single-Operatoren mischst.

    die frage war: ist es praktisch möglich, delete so zu implementieren, dass es ein notwendiges delete[] selbst erkennt und ausführt? es also egal ist, ob man delete oder delete[] aufruft?



  • [][][] schrieb:

    CStoll schrieb:

    new/delete braucht die Information nicht, wieviele Elemente das Array nun hat (es ist immer genau 1), also wozu sollte es diese Information noch speichern? Das kostet nur Speicherplatz und Rechenzeit.

    es sollte diese information speichern, weil eine unterscheidung zwischen delete und delete[] fehleranfällig ist und weil es die syntax komplizierter als notwendig macht.

    die erhöhte rechenzeit zur abfrage eines bits halte ich für kaum relevant, wenn man den üblichen aufwand für eine (de-)allokation in relation dazu setzt.
    der speicherbedarf stiege in der tat an, günstigenfalls aber nur um ein bit (nicht gesetzt = nur ein element), das sich auf vielen plattformen bestimmt noch in den ohnehin vorhandenen verwaltungsinformationen unterbringen ließe.

    Ein einzelnes Bit kann nicht alleine untergebracht werden, also mußt du mindestens ein Byte für die Information "ich bin ein Array" veranschlagen - aus Padding-Gründen meist noch mehr. Und dieses mußt du so anlegen, daß es nicht mit irgendwelchen Nutz-Informationen kollidiert.

    und für sehr kleine allokationen könnte man ja immer noch in eigener verantwortung auf malloc und free zurückgreifen.

    Und auf die Vorteile von new/delete (automatische Ctor/Dtor-Aufurfe etc) verzichten? Nein danke.

    gibt es wirklich keine besseren gründe für eine unterscheidung außer den genannten?

    Wenn dir die genannten Gründe nicht gut genug sind, bist du selber Schuld 😉 Mir reichen sie.

    CStoll schrieb:

    der ANSI-Standard legt nur fest, daß es undefiniert ist, wenn du Array- und Single-Operatoren mischst.

    die frage war: ist es praktisch möglich, delete so zu implementieren, dass es ein notwendiges delete[] selbst erkennt und ausführt? es also egal ist, ob man delete oder delete[] aufruft?

    Man könnte die Aufrufe von der Single Operatoren auf die entsprechenden Array-Operatoren umbiegen, das löst zumindest die Speicherverteilung. Aber ob man das Verhalten von new/delete direkt beeinflussen kann, bin ich mir nicht sicher (delete ruft einen Dtor auf und übergibt anschließend den Speicher an op delete; delete[] ermittelt die Arraygröße, ruft n Dtoren auf und übergibt den Speicher an op delete[]).



  • CStoll schrieb:

    Ein einzelnes Bit kann nicht alleine untergebracht werden, also mußt du mindestens ein Byte für die Information "ich bin ein Array" veranschlagen - aus Padding-Gründen meist noch mehr. Und dieses mußt du so anlegen, daß es nicht mit irgendwelchen Nutz-Informationen kollidiert.

    praktisch wirst du auf den meisten plattformen für dieses eine bit den platz finden. gibt es überhaupt eine plattform, auf der ein new[std::numeric_limits<size_t>::max() >> 1] nicht fehlschlägt?e

    CStoll schrieb:

    und für sehr kleine allokationen könnte man ja immer noch in eigener verantwortung auf malloc und free zurückgreifen.

    Und auf die Vorteile von new/delete (automatische Ctor/Dtor-Aufurfe etc) verzichten? Nein danke.

    das ist doch praktisch überhaupt nicht relevant. wie oft legst du so kleine objekte mit new an, dass der entstehende overhead für die anzahl überhaupt ins gewicht fällt?

    Wenn dir die genannten Gründe nicht gut genug sind, bist du selber Schuld 😉 Mir reichen sie.

    vielleicht ist mir ist konsistenz und sicherer code wichtiger als dir.
    ich gebe allerdings zu, von den heutigen maßstäben bei speicherausbau und rechengeschwindigkeit verwöhnt zu sein. allerdings wird auf einem rechner der c64-klasse c++ (oder dynamische speicherallokation überhaupt) nicht sehr sinnvoll sein.



  • [][][] schrieb:

    CStoll schrieb:

    Ein einzelnes Bit kann nicht alleine untergebracht werden, also mußt du mindestens ein Byte für die Information "ich bin ein Array" veranschlagen - aus Padding-Gründen meist noch mehr. Und dieses mußt du so anlegen, daß es nicht mit irgendwelchen Nutz-Informationen kollidiert.

    praktisch wirst du auf den meisten plattformen für dieses eine bit den platz finden. gibt es überhaupt eine plattform, auf der ein new[std::numeric_limits<size_t>::max() >> 1] nicht fehlschlägt?e

    Und wo soll dieser Platz deiner Meinung nach sein? Im allokierten Objekt geht nicht (schließlich weißt du nicht, ob und welche Bereiche des Objekts ungenutzt sind), dahinter auch nicht (bei Arrays wärst du dann im nächsten Objekt), also bleibt nur davor - und da mußt du darauf achten, daß du die Adressen "richtig" im Speicher ausrichtest, sonst wird es wirlich langsam.

    das ist doch praktisch überhaupt nicht relevant. wie oft legst du so kleine objekte mit new an, dass der entstehende overhead für die anzahl überhaupt ins gewicht fällt?

    Zum Glück entscheidest nicht du, was relevant ist und was nicht. Das hängt immer von der Anwendung ab (und z.B. bei Listen oder Bäumen ist der Overhead schon erheblich, bei jedem Knoten noch die Anzahl 1 zu speichern).

    Wenn dir die genannten Gründe nicht gut genug sind, bist du selber Schuld 😉 Mir reichen sie.

    vielleicht ist mir ist konsistenz und sicherer code wichtiger als dir.

    Konsistenz erreichst du aber eher damit, daß du auf nackte Speicher-Verwaltung verzichtest - nimm lieber std::vector<> und Kollegen, die kapseln solche Probleme für dich 😉



  • CStoll schrieb:

    Und wo soll dieser Platz deiner Meinung nach sein?

    hab doch schon ein beispiel gebracht. im obersten bit der allokationsgröße wäre auf den meisten plattformen eine möglichkeit.

    Konsistenz erreichst du aber eher damit, daß du auf nackte Speicher-Verwaltung verzichtest - nimm lieber std::vector<> und Kollegen, die kapseln solche Probleme für dich 😉

    es geht aber nicht um die standardbibliothek sondern um delete[]. wenn ich ausschließlich delete[] benutzen würde (bescheuert aber legitim), wäre das konsistent.

    wenn ich einen smartpointer schreiben könnte, der ohne einschränkungen sowohl für einzelne objekte als auch für arrays funktionieren würde, dann wäre das konsistent. geht aber ned, weil ich blöderweise zwischen delete und delete[] unterscheiden muss.



  • [][] schrieb:

    CStoll schrieb:

    Und wo soll dieser Platz deiner Meinung nach sein?

    hab doch schon ein beispiel gebracht. im obersten bit der allokationsgröße wäre auf den meisten plattformen eine möglichkeit.

    Ja, jetzt brauchst du nur noch eine plattformübergreifende Möglichkeit, da ranzukommen 😉 (wo malloc/free sich die Größe des Blocks merken, ist nicht standardisiert)



  • [][][][][][], Du ignorierst geflissen die Kern-Philosophie von C++: „Don't pay for what you don't use.“ Ganz egal, wie gering das zusätzliche Overhead wäre, es ist inakzeptabel. Aus genau demselben Grund ist ja z.B. auch der Destruktor einer Klasse nicht standardmäßig virtuell, obwohl das viele Sachen erleichtern würde.



  • CStoll schrieb:

    Ja, jetzt brauchst du nur noch eine plattformübergreifende Möglichkeit, da ranzukommen 😉 (wo malloc/free sich die Größe des Blocks merken, ist nicht standardisiert)

    uff. ich mag mich hier echt nicht um jedes einzelne bit zanken. ich bin mir sicher, dass irgendein cleverer bursche schon einen mechanismus dafür gefunden hätte, die plattform für die verwaltung der objektanzahl zuständig zu machen und die damit die möglichkeit hätte, sie sehr effizient zu verwalten.

    und ich bin mir auch sicher, dass sich kein programmierer darüber beschwert hätte. und sich damit viele tausend stunden debugging hätten einsparen lassen.

    in anderen sprachen hat man deshalb so späße wie garbage collectors eingebaut, damit man sich ums freigeben nicht mehr explizit kümmern muss.



  • Ja, es gibt etwa ein halbes Dutzend mögliche Strategien, die Größe eines allokierten Speicherblocks zu speichern (grob geschätzt). Und in der Regel sind die new/delete eines Compilers auch darauf ausgerichtet, mit dessen Speicherverwaltung zurechtzukommen (wobei nicht einmal festgelegt ist, daß new intern auf malloc() aufbauen muß). Der Kernpunkt dieser Diskussion ist, daß jedes System hinter den Kulissen sein eigenes Süppchen kochen kann - und wenn du dich nicht auf eins festlegen willst, mußt du alle Möglichkeiten kennen.



  • Konrad Rudolph schrieb:

    [][][][][][], Du ignorierst geflissen die Kern-Philosophie von C++: „Don't pay for what you don't use.“

    stimmt. aber fehlersuche wegen falschem delete kostet auch etwas, wenn auch nicht rechenzeit oder speicherplatz. und den extracode, den man dann und wann wegen der unterscheidung schreiben muss, gibts auch nicht kostenlos.

    Ganz egal, wie gering das zusätzliche Overhead wäre, es ist inakzeptabel. Aus genau demselben Grund ist ja z.B. auch der Destruktor einer Klasse nicht standardmäßig virtuell, obwohl das viele Sachen erleichtern würde.

    ein unterschied ist: für virtuelle destruktoren musste kein neues keyword eingeführt werden (ich sehe delete[] mal als eigenes keyword). der kosten-/nutzenfaktor ist günstiger.


Anmelden zum Antworten