Importer:Error:WasEmpty



  • Hallo,

    Arrays gibt man i. d. R. über eine Klasse zurück (call_by_value). Ich probiere gerade über eine andere Methode – nämlich über einen Zeiger es hinzubekommen und hänge hier noch fest …

    Ich weiß nicht, ob das Löschen des Zeigers auf die Art OK ist, oder ob ich dabei Speicherlecks produziere. Außerdem ist es in Ordnung den Code so zu schreiben, oder ist das nicht üblich?

    #include <iostream>
    using namespace std;
    
    int* inigetArray(){
        int *p_array = new int[10];
        int *p_merker = p_array;                    // Anfangsadresse merken
        for(int i=0; i<10; i++){
            *p_array = i;
            p_array++;
        }
        return p_merker;
    }
    
    void showArray(int *p_array){
        int *p_merker = p_array;                    // Anfangsadresse merken
        for( int i=0; i<10; i++){
            cout << i << ".:   " << *p_array << endl;
            p_array++;
        }
        delete p_merker;                            // Speicherleck?
    }
    
    int main(int argc, char** argv){
    
        showArray(inigetArray());
    
        return 0;
    }
    


  • Das kann man so machen, sollte man aber nicht. Das Problem ist, dass die Besitzverhältnisse hier sehr schnell unklar werden, und man damit doppelt oder gar nicht deleted. z.B:

    #include <iostream>
    using namespace std;
    
    int* inigetArray(){
        int *p_array = new int[10];
        int *p_merker = p_array;                    // Anfangsadresse merken
        for(int i=0; i<10; i++){
            *p_array = i;
            p_array++;
        }
        return p_merker;
    }
    
    void showArray(int *p_array){
        int *p_merker = p_array;                    // Anfangsadresse merken
        for( int i=0; i<10; i++){
            cout << i << ".:   " << *p_array << endl;
            p_array++;
        }
        delete[] p_merker;                            // Speicherleck?
    }
    
    int main(int argc, char** argv){
        int* p = inigetArray();
        showArray(p);
        delete[] p; // Oups! Zwei mal gelöscht
        return 0;
    }
    

    Deshalb verwendet man für so etwas immer Klassen, die den Besitz verwalten. Heißt z.B. std::vector<int>, oder std::unique_ptr<int[]>.


  • Mod

    Cpp_Anfaeger schrieb:

    Ich weiß nicht, ob das Löschen des Zeigers auf die Art OK ist, oder ob ich dabei Speicherlecks produziere. Außerdem ist es in Ordnung den Code so zu schreiben, oder ist das nicht üblich?

    Absolut unüblich. Mach das niemals so!

    Außerdem: Nutze kein new[] für Arrays! std::vector ist die weit überlegene Alternative, mit vielen Vorteilen, ohne die Nachteile. Einen vector kannst du auch ganz intuitiv als Rückgabewert einer Funktion benutzen, so wie du es von anderen Datentypen kennst.

    Falls die arrays statische Größe haben (so wie hier, da 10 eine Konstante ist), kannst du auch std::array nehmen. Da gilt das gleiche wie beim vector: Die Vorteile eines "normalen" statischen Arrays, ohne die Nachteile. Und ebenfalls Wertsemantik (d.h. kann unter anderem als Rückgabewert benutzt werden). std::array wird oftmals im Zusammenhang mit Templates benutzt, die du vermutlich noch nicht kennst. Dann ist man flexibler, was den genauen Wert der Feldgröße angeht.
    Das std::array ist nichts anderes als eine Klasse um ein "normales" Array, wie du es bereits kennst. Bloß brauchst du es nicht mehr selber programmieren, da es Teil der Standardbibliothek ist.

    Und eine Abstraktionsstufe weiter wäre es, Iteratoren zu benutzen (daüfr braucht man wieder Templates), wie bei der Standardbibliothek (z.B. Funktionen aus Header algorithms). Dann ist deine Funktion völlig unabhängig davon, was für eine Art Container überhaupt benutzt wird und kommt mit allem zurecht. Zur Not auch mit "normalen" Arrays. Der Nutzer der Funktion entscheidet.



  • delete ist hier auch falsch. Es müsste delete[] sein. Zu jedem ausgeführten new gehört ein delete , zu jedem new[] gehört ein delete[] . Für die direkte Verwendung von new[] gibt es spätestens seit C++11 keinen guten Grund mehr, da Dinge wie std::vector "movable" sind. Und ab C++14 kannst du auch auf direkte Verwendung von new verzichten, weil wir dann in der Standardbibliothek make_unique<T>(...) und make_shared<T>(...) haben werden, was all das abdecken müsste, wo wir sonst vorher new T(...) verwendet haben. (make_shared gibt es schon ab C++11)

    Der Grund, warum man das new/new[]/delete/delete[] vermeiden sollte, ist ganz einfach der, dass man sich mit den Alternativen viel weniger Sorgen um so Dinge wie Speicherlecks machen muss. Man spart sich typischerweise auch Schreibarbeit, gerade wenn man mit Ausnahmen arbeitet, da Du sonst mehr mit try/catch arbeiten und dann im Ausnahmefall Resourcen manuell wieder freigeben müsstest.



  • Danke für den Hinweis, vector u. unique_ptr als dynamisches Array kannte ich noch nicht. Ich werde mich die Tage genauer damit beschäftigen. Gehören die Klassen zum C++ Standard, oder ist nur das std::array standard? Oder ist das einfach eine Klasse ähnlich wie in VC++ CStringArray?

    krümelkacker schrieb:

    delete ist hier auch falsch. Es müsste delete[] sein. Zu jedem ausgeführten new gehört ein delete , zu jedem new[] gehört ein delete[] .

    Zum Verständnis über Zeiger im obigen Code und das Löschen mit delete[]:

    schreibe ich: delete p_merker, gibt mir der Compiler 4 Byte frei, weil ich in der Funktion inigetArray() doch nur eine Adresse zurück gebe, oder?

    Woher weiß der Compiler in Zeile 20 (wenn ich delete[] p_merker verwenden würde), wie viel Byte Speicher das Array hat u. dementsprechend wie viel Speicher er löschen muss?


  • Mod

    Cpp_Anfaeger schrieb:

    Danke für den Hinweis, vector u. unique_ptr als dynamisches Array kannte ich noch nicht. Ich werde mich die Tage genauer damit beschäftigen. Gehören die Klassen zum C++ Standard, oder ist nur das std::array standard? Oder ist das einfach eine Klasse ähnlich wie in VC++ CStringArray?

    Das sind allesamt Klassen aus der C++-Standardbibliothek. Gerade vector ist wohl einer der am häufigsten benutzten Teile der Standardbibliothek und sollte daher unbedingt bekannt sein. unique_ptr + new ist hingegen eher exotisch und wurde wohl nur erwähnt wegen ein paar winzigkleinen Unterschieden zwischen vector und der Kombo unique_ptr + new, über die sich sonst irgendein Klugscheißer beschwert hätte. Ansonsten machen beide praktisch das gleiche, aber vector ist viel komfortabler.

    krümelkacker schrieb:

    Woher weiß der Compiler in Zeile 20 (wenn ich delete[] p_merker verwenden würde), wie viel Byte Speicher das Array hat u. dementsprechend wie viel Speicher er löschen muss?

    Der Compiler weiß davon gar nichts, der ist nur dafür zuständig, dein Programm zu übersetzen. Die Funktionen new und delete sind es hingegen, die das wissen (oder genauer gesagt, die Betriebssystemfunktionen, die hinter new und delete stecken und von diesen benutzt werden). Das machen sie ganz einfach, indem sie sich diese Daten irgendwo merken. Gängige Methoden sind, dass sie eine (globale) Tabelle mit Verwaltungsdaten führen oder aber, dass sie vor den reservierten Bereich ein paar Daten über die Größe dieses Bereichs schreiben.



  • Cpp_Anfaeger schrieb:

    Gehören die Klassen zum C++ Standard, oder ist nur das std::array standard? Oder ist das einfach eine Klasse ähnlich wie in VC++ CStringArray?

    Du kannst dir merken, das in der Regel alle Klassen im Namensraum std auch Teil der Standardbibliothek von C++ sind (Es gibt Ausnahmen, bei denen dann doch spezifische Implementierungen enthalten sind, das sollte sich aber in Grenzen halten).



  • SeppJ schrieb:

    Das sind allesamt Klassen aus der C++-Standardbibliothek. Gerade vector ist wohl einer der am häufigsten benutzten Teile der Standardbibliothek und sollte daher unbedingt bekannt sein. unique_ptr + new ist hingegen eher exotisch und wurde wohl nur erwähnt wegen ein paar winzigkleinen Unterschieden zwischen vector und der Kombo unique_ptr + new, über die sich sonst irgendein Klugscheißer beschwert hätte. Ansonsten machen beide praktisch das gleiche, aber vector ist viel komfortabler.

    danke, ja ich meinte den Befehl, den der Compiler umsetzt. Alles klar, grob genommen ist alles std standard.

    Was ich fragen wollte im 2. Teil, ist aber eigentlich:

    Löscht der Befehl delete[] p_merker in meinem Code wirklich das komplette Array, weil ich als Übergabeparameter in der Funktion showArray nur einen *Zeiger int p_array habe:

    #include <iostream>
    using namespace std;
    
    int* inigetArray(){
        int *p_array = new int[10];
        int *p_merker = p_array;                    // Anfangsadresse merken
        for(int i=0; i<10; i++){
            *p_array = i;
            p_array++;
        }
        return p_merker;
    }
    
    void showArray(int *p_array){
        int *p_merker = p_array;                    // Anfangsadresse merken
        for( int i=0; i<10; i++){
            cout << i << ".:   " << *p_array << endl;
            p_array++;
        }
        delete[] p_merker;                            // immer noch Speicherleck?
    }
    
    int main(int argc, char** argv){
    
        showArray(inigetArray());
    
        return 0;
    }
    

  • Mod

    Cpp_Anfaeger schrieb:

    Löscht der Befehl delete[] p_merker in meinem Code wirklich das komplette Array, weil ich als Übergabeparameter in der Funktion showArray nur einen *Zeiger int p_array habe:

    Ja, was solltest du denn sonst haben, anstatt eines Zeigers?

    Aber nochmal: Auch wenn dieses Programm jetzt korrekt abläuft, so ist diese Methode sehr, sehr fehleranfällig. Wenn du Ressourcen (z.B. Speicher) verwaltest, dann benutze immer spezialisierte Halterklassen, die mit RAII programmiert wurden (und zwar nur eine Ressource pro Instanz!). Das ist nicht nur einfacher in der Benutzung, sondern auch äußerst robust gegen Fehler. Die genannten std::vector (für dynamische Felder) und std::unique_ptr (für alle möglichen Arten von dynamischem Speicher) sind jeweils solche Klassen. Schon fertig in der Standardbibliothek verfügbar, weil man dies (zumindest den vector, den unique_ptr eher seltener) so oft benötigt.



  • SeppJ schrieb:

    Cpp_Anfaeger schrieb:

    Löscht der Befehl delete[] p_merker in meinem Code wirklich das komplette Array, weil ich als Übergabeparameter in der Funktion showArray nur einen *Zeiger int p_array habe:

    Ja, was solltest du denn sonst haben, anstatt eines Zeigers?

    Aber nochmal: Auch wenn dieses Programm jetzt korrekt abläuft, so ist diese Methode sehr, sehr fehleranfällig. Wenn du Ressourcen (z.B. Speicher) verwaltest, dann benutze immer spezialisierte Halterklassen, die mit RAII programmiert wurden (und zwar nur eine Ressource pro Instanz!). Das ist nicht nur einfacher in der Benutzung, sondern auch äußerst robust gegen Fehler. Die genannten std::vector (für dynamische Felder) und std::unique_ptr (für alle möglichen Arten von dynamischem Speicher) sind jeweils solche Klassen. Schon fertig in der Standardbibliothek verfügbar, weil man dies (zumindest den vector, den unique_ptr eher seltener) so oft benötigt.

    Nee nee klar, ich weiß, dass es viel sauberer ist, a la vector & co zu codieren. Ich wollte hier nur mit Zeigern rumprobieren, um den Effekt besser zu verstehen.

    OK danke, ich dachte nur, dass ich in der Funktion inigetArray() insgesamt 40 Byte reserviert habe, und in der Funktion showArray aber nur 4 Byte gelöscht habe, weil der Zeiger von dieser zweiten Funktion ja nur noch 4 Byte groß ist. So blieben nach meiner Überlegung 36 Byte als Leck unreichbar.



  • Ich bin mir nicht sicher aber ich glaube ein Pointer ist immer gleich gross, es geht nur darum eine Speicheradresse zu speichern.

    Das Objekt befindet sich dann an der Speicheradresse auf die der Pointer zeigt.

    Deswegen kannst du auch Pointer ziemlich billig per return uebergeben oder e.g. ein vector<class *> vec; machen um bei einer (Vergroesserung) des Vectors nur die Pointer und nicht die ganzen Objekte zu kopieren auf die die Pointer zeigen.

    Was Speicherlecks angeht gibt es glaube ich 2 grundsaetzliche Probleme.

    1. Du hast den Pointer die ganze Zeit noch in der Hand und vergisst einfach delete auzurufen.

    2. Du verlierst den letzten Pointer der noch auf das Objekt zeigt und bist nicht mehr in der Lage delete aufzurufen.

    Bsp.: vector<class *> vec;

    Wenn der Vector out of scope geht oder mit vec.clear(); geleert wird hast du ein Speicherleck.
    Du muesstest bei der Konstellation erst manuell mit einer Schleife durch alle Pointer gehen und einzeln delete aufrufen.

    Bsp.: vector<unique_ptr <class> > vec;
    Hier hast du den std unique pointer der das Objekt automatisch wegraeumt sobald es out of scope geht.
    Hier ist aber z.b. auch wichtig zu wissen, dass der Vector, wenn er als Member in einer Klasse deklariert wurde, erst out of scope geht, sobald das Objekt zerstoert wurde. <-- Das ist mir mal passiert.



  • Ruvi schrieb:

    Was Speicherlecks angeht gibt es glaube ich 2 grundsaetzliche Probleme.

    1. Du hast den Pointer die ganze Zeit noch in der Hand und vergisst einfach delete auzurufen.

    2. Du verlierst den letzten Pointer der noch auf das Objekt zeigt und bist nicht mehr in der Lage delete aufzurufen.

    Bsp.: vector<class *> vec;

    Wenn der Vector out of scope geht oder mit vec.clear(); geleert wird hast du ein Speicherleck.
    Du muesstest bei der Konstellation erst manuell mit einer Schleife durch alle Pointer gehen und einzeln delete aufrufen.

    Bsp.: vector<unique_ptr <class> > vec;
    Hier hast du den std unique pointer der das Objekt automatisch wegraeumt sobald es out of scope geht.
    Hier ist aber z.b. auch wichtig zu wissen, dass der vector wenn er als member in einer Klasse deklariert wurde erst out of scope geht sobald, dass Objekt zerstoert wurde. <-- Das ist mir mal passiert.

    Ja, das genau darum gehts mir. Wenn ich mich in demselben Scope befinde:

    *int p_array = new int[10];

    und delete [] p_array - wie von krümelkacker vorgeschlagen - aufrufe, wird nach meinem Verständis das komplette Array gelöscht, da die gesamte Arraygröße im Scope bekannt ist.

    Sobald ich aber die Funktion verlasse und nur noch eine Adresse zurückgebe (Zeiger), dann weiß die nächste Funktion doch nicht, wie groß das Array ist, und sollte demnach nicht das gesamt Array freigeben können.

    Man müsste dann wie von dir vorgeschlagen mittels einer Schleife die einzelnen Zeigerelemente löschen, habe ich es richtig verstanden?



  • Nein, ein delete[] reicht. Du hast immer nur einen Pointer, auch wenn du das im gleichen Scope machen würdest.



  • cooky451 schrieb:

    Nein, ein delete[] reicht. Du hast immer nur einen Pointer, auch wenn du das im gleichen Scope machen würdest.

    Ich bin aber schon in der zweiten Funktion - also nicht mehr im selben Scope.

    *Edit: Achso, es reicht demnach nur die Anfangsadresse aufzulösen und das OS gibt alles Folgende frei? Woher weiß das OS, wie viel Bytes nachfolgend freigegeben werden müssen, wenn ich außerhalb des Scope bin? Oder ist das irgendwie bei der Übergabe (return) irgendwo hinterlassen?



  • Ein delete[] reicht aus.

    Mir ist nicht ganz klar woher delete[] weiss wieviele Elemente im Array sind, aber ich wuerde vermuten diese Information befindet sich entweder im Pointer selbst oder an der Speicheradresse auf die der Pointer zeigt, also im Array selbst.

    http://www.cplusplus.com/reference/new/operator delete[]/
    "operator delete[] can be called explicitly as a regular function, but in C++, delete[] is an operator with a very specific behavior: An expression with the delete[] operator, first calls the appropriate destructors for each element in the array (if these are of a class type), and then calls function operator delete[] (i.e., this function) to release the storage."

    Im Grunde gilt, alles was mit "new" geholt wird, muss auch wieder mit delete oder delete[] geloescht werden, ansonsten hat man ein Speicherleck.

    Bei meinem ersten Beispiel oben mit dem Vector of Pointers habe ich also einen Vector voller Pointer die auf Objekte zeigen die mit "new" geholt wurden.

    Wenn der Vector jetzt out of scope geht oder ich vec.clear(); benutze werden alle Objekte die auf dem Stack liegen (die Pointer) ordnungsgemaess geloescht.

    Allerdings liegen die Objekte die mit "new" geholt wurden auf dem Heap und dort wird es erst mit dem Befehl "delete" weggeraeumt.

    Das bedeutet, dass diese Objekte erst manuell geloescht werden muessen bevor ich den letzten Pointer verliere der auf diese jeweiligen Objekte zeigt.



  • Ruvi schrieb:

    Ein delete[] reicht aus.

    Mir ist nicht ganz klar woher delete[] weiss wieviele Elemente im Array sind, aber ich wuerde vermuten diese Information befindet sich entweder im Pointer selbst oder an der Speicheradresse auf die der Pointer zeigt, also im Array selbst.

    http://www.cplusplus.com/reference/new/operator delete[]/
    "operator delete[] can be called explicitly as a regular function, but in C++, delete[] is an operator with a very specific behavior: An expression with the delete[] operator, first calls the appropriate destructors for each element in the array (if these are of a class type), and then calls function operator delete[] (i.e., this function) to release the storage."

    Danke, es klingt logisch, eventuell ist es im Array selbst hinterlassen.



  • Ruvi schrieb:

    Mir ist nicht ganz klar woher delete[] weiss wieviele Elemente im Array sind, aber ich wuerde vermuten diese Information befindet sich entweder im Pointer selbst oder an der Speicheradresse auf die der Pointer zeigt, also im Array selbst.

    Der Compiler (g++) reserviert +4 bytes (sizeof(size_t)) vor dem array und speichert dort die Anzahl der Elemente.

    #include <iostream>
    #include <new>
    
    class test
    {
        int i;  // 4 bytes
    public:
        test() {std::cout << "ctor" << std::endl;}
        ~test() {std::cout << "dtor" << std::endl;}
    
        void* operator new[](size_t n)
        {
            void* result;
    
            result = ::operator new[](n);
            std::cout << "operator new[] size:" << n << " at:" << result << std::endl;
    
            return result;
        }
    };
    
    int main(int, char**)
    {
        test* p = new test[2];
    
        std::cout << "new test[2] at:" << p << std::endl;
    
        delete[] p;
    
        return 0;
    }
    
    operator new[] size:12 at:0x7e1050
    ctor
    ctor
    new test[2] at:0x7e1054
    dtor
    dtor
    

    - osdt



  • Danke, gut zu wissen.



  • osdt schrieb:

    Der Compiler (g++) reserviert +4 bytes (sizeof(size_t)) vor dem array und speichert dort die Anzahl der Elemente.

    #include <iostream>
    #include <new>
    
    class test
    {
        int i;  // 4 bytes
    public:
        test() {std::cout << "ctor" << std::endl;}
        ~test() {std::cout << "dtor" << std::endl;}
    
        void* operator new[](size_t n)
        {
            void* result;
    
            result = ::operator new[](n);
            std::cout << "operator new[] size:" << n << " at:" << result << std::endl;
    
            return result;
        }
    };
    
    int main(int, char**)
    {
        test* p = new test[2];
    
        std::cout << "new test[2] at:" << p << std::endl;
    
        delete[] p;
    
        return 0;
    }
    

    Danke für den Hinweis,

    ich wollte gerade den Code testen. Mein VC++6 sagt allerdings, dass in Zeile 15 der Befehl new[] kein Element von 'global namespace' ist. Man müsste den Operator nicht aus std entnehmen, sondern global lassen?



  • Dann ist was falsch, denn der Code läuft: http://ideone.com/kCe65G



  • Danke, der Code läuft unter Netbeans einwandfrei ... schient irgendetwas mit meiner heißgeliebten VC6 IDE zu sein http://lug-ottobrunn.de/wiki/Operator_New_in_CPP


Anmelden zum Antworten