SOLVED - Realloc - Wird der Inhalt kopiert?



  • SOLVED - 20150806: Hab den Thread neu auf gemacht, weil es wieder um realloc geht - aber weiter geht es in Nachricht #7

    Hallo.

    Ich habe gelesen, dass bei einer Verkleinerung des Speicherbereichts mit Realloc einfach der überflüssige Speicher freigegeben wird. Im gleichen Artikel (hatte den jetzt als seriös eingestuft - Link weiter unten) heißt es, dass bei einer Vergrößerung einfach etwas hinten angehängt wird.
    Wie ist das, wenn die Speicheradressen hinter meinem Array im Heap statisch reserviert sind und ich dieses Array vergrößere - wird dann der Inhalt kopiert bzw. befindet sich der Inhalt zu einem Zeitpunkt zweimal im Speicher? Eigentlich ist das ja beim Arbeiten mit Zeigerarithmetik garnicht anders möglich, oder?

    Ich frage, weil ich ein sehr großes Array vergrößern möchte. Also, wenn ein Array ohnehin schon 70% meines Arbeitsspeichers belegt, kann ich es mit Realloc noch vergrößern, ohne dass mein Programm anfängt zu swapen?

    http://openbook.rheinwerk-verlag.de/c_von_a_bis_z/014_c_dyn_speicherverwaltung_008.htm

    Grüße,
    CJens


  • Mod

    Wenn hinten nichts angehängt werden kann, dann wird versucht, einen größeren Bereich zu besorgen und die alten Daten an dessen Anfang zu kopieren. Der Rückgabewert von realloc gibt dir einen Zeiger auf diesen neuen Bereich, der alte Bereich wird von realloc freigegeben und ist danach nicht mehr gültig*.

    Also, wenn ein Array ohnehin schon 70% meines Arbeitsspeichers belegt, kann ich es mit Realloc noch vergrößern, ohne dass mein Programm anfängt zu swapen?

    Vielleicht, vielleicht nicht. Wenn realloc kopieren muss, wird der physische Speicher während des Kopiervorgangs nicht ausreichen.

    http://openbook.rheinwerk-verlag.de/c_von_a_bis_z/014_c_dyn_speicherverwaltung_008.htm

    Hände weg von Jürgen Wolf und dem ganzen Galileo-Verlag! Die Qualität dieser Bücher ist extrem mies. Fast alle dummen Fragen hier im Forum sind auf dieses Drecksbuch zurück zu führen.

    *: Wichtig: Falls kein größerer Bereich besorgt werden kann (etwa weil es nicht mehr genug Speicher gibt), dann scheitert realloc und gibt einen Nullzeiger zurück. Dafür bleibt aber der alte Speicherbereich nach wie vor gültig und muss nach wie vor freigegeben werden!



  • Ist leider echt so. Jeder Wolf-Monat kostet einen zwei Monate des Entwolfens.

    Das ist jetzt nichts "Ideologisches", wie wo man den '' setzt bei "int a" oder "int *a", sondern es geht darum, daß C++ die Fehlersuchzeit reduziert. SeppJ, ich, und alle Konifereren hier, wir alle hatten ca zehnmal so viel Fehlersuchzeit wie Tippzeit. Nach dem Lesen von Meyers ist unsere Produktivität explodiert. Uups. Immernoch ca zehnmal so viel Fehlersuchzeit wie Tippzeit. Ätsch. Aber fast keine Fehler mehr beim Kunden. Passt.

    Wobei ich behaupte, daß ich dahingehend fertig bin und keine Fehler mehr ausliefere.



  • ...ihr könnt mich beglückwünschen - vor fünf Tage habe ich mein Buch "C++ - Das umfassende Handbuch" erhalten - von Jürgen Wolf 🙂

    Aber mir ist das auch schon aufgefallen. Hatte von ihm das Buch "C++ Grundkurs" und darin stand nichtmal, wie man eine Datei öffnet und lesen kann.

    Aber danke für die Infos. Bin jetzt wirklich schlauer - auch wenn ich mir eine andere Antwort wie z.B. "Wolf ist super" oder "realloc kann zaubern" gewünscht hätte. Aber immerhin ist es besser wie in C# - dort wird immer kopiert (meines Wissens (nicht von Wolf) nach) - auch beim Verkleinern des Speicherbereichs.



  • CJens schrieb:

    ...ihr könnt mich beglückwünschen - vor fünf Tage habe ich mein Buch "C++ - Das umfassende Handbuch" erhalten - von Jürgen Wolf 🙂
    [...]
    Aber danke für die Infos. Bin jetzt wirklich schlauer - auch wenn ich mir eine andere Antwort wie z.B. "Wolf ist super" oder "realloc kann zaubern" gewünscht hätte.

    That's the spirit! 🙂
    Krebs, Bein ab, oder nix zu fressen ist schlimmer...

    [spekulation]Eine neue Auflage ist evtl. auch gar nicht so schlimm.[/spekulation]

    Allerdings bin ich verblüfft, dass Dir das nach 5 Jahren Mitgliedschaft hier passiert....

    PS: realloc() kann zaubern! 😉



  • CJens schrieb:

    Aber danke für die Infos. Bin jetzt wirklich schlauer - auch wenn ich mir eine andere Antwort wie z.B. "Wolf ist super" oder "realloc kann zaubern" gewünscht hätte. Aber immerhin ist es besser wie in C# - dort wird immer kopiert (meines Wissens (nicht von Wolf) nach) - auch beim Verkleinern des Speicherbereichs.

    realloc kann zaubern und kopiert immer, wenn der Speicherbreich sich ändert. D.h. du musst dich nicht kümmern, wenn die Adresse sich ändert.

    Die Benutzung von realloc ist leider falsch in vielen Tutorials & Büchern (auch bei unserem Freund Wolf). In der Regel sieht du Sachen wie

    buffer = realloc(buffer, newsize * sizeof(datatype));
    

    Ist in meinen Augen nicht korrekt, da wenn realloc den Speicher nicht reservieren kann, wird der original Speicher nicht angefasst und realloc liefert NULL zurück. In diesem Fall verlierst du die Referenz auf den original Speicher und hast du dann ein memory leak. Wenn dein Program wegen des NULL s sofort endet, ist das kein großes Problem, aber wenn dein Program nicht sofort beendet sondern weiter arbeitet und realloc immer wieder NULL zurückliefert, dann hast du ein Problem.

    Deshalb besser so realloc aufrufen:

    int *buffer, *buff_tmp;
    int size = ???;
    
    buffer = malloc(size * sizeof *buffer);
    if(buffer == NULL)
    {
      // Fehler Behandlung, weitere Ausfürung des Codes
      // verhindern
    }
    
    ...
    
    int newsize = 3 * size;
    
    buff_tmp = realloc(buffer, newsize * sizeof *buff_tmp);
    if(buff_tmp == NULL)
    {
      // Fehler Behandlung, weitere Ausfürung des Codes
      // verhindern. "buffer" ist noch reserviert, die Referenz
      // darauf nicht verloren gegangen und kannst ggf. weiter arbeiten
    } else
      buffer = buffer_tmp;  // buffer_tmp ist die neue Referenz auf den
                            // Speicher, du kannst die alte Referenz ignorgieren
    

    Ich weiß, ein bisschen mehr Code als ein Einzeiler wie buffer = realloc(buffer, newsize * sizeof(datatype)); dafür aber garantiert sicherer.

    Achte auf darauf, dass ich sizeof *buffer nehme und nicht sizeof int : sizeof ist ein Operator der zur Compilezeit ausgewertet wird, auch wenn buffer ürsprünglich aufs Nirvana zeigt, ist sizeof *buffer wohldefiniert und liefert die richtige Anzahl von Bytes. Der Vorteil ist, solltest du irgendwann auf die Idee kommen, ein anderes kompatibles Datentyp zu verwenden (wie uint64_t , dann brauchst du nur die erste Zeile zu verändern

    // von int *buffer, *buff_tmp;   zu
    uint64_t *buffer, *buff_tmp;
    
    ...
    

    und den Rest musst du nicht mehr anpassen.



  • Hab es gelöst... lass den Kommentar aber mal stehen. Hab den Code auch korrigiert, weil der Fehler zu peinlich war 😉

    Danke für die Infos. In meinem Fall ist es egal - wenn realloc den Speicher nicht reservieren kann, dann kann von mir aus das ganze Programm abstürzen 😉

    Ich habe jetzt gleich wieder ein Problem mit Realloc. Die Funktion soll einfach ein bereits bestehendes (beim ersten Mal besteht es noch nicht) Array um ein Element erweitern und das neue Element initialisieren. Crashzeile habe ich durch nen Kommentar markiert.

    typedef struct TSubset{
    	TNode** ppNode;
    	unsigned long nNode;
    
    	TEdge** ppEdge;
    	unsigned long nEdge;
    
    	TFace** ppFace;
    	unsigned long nFace;
    
    	TCell** ppCell;
    	unsigned long nCell;
    }TSubset;
    
    typedef struct TFamily{
    	char* Name;
    
    	struct TSubset Mesh;
    
    	unsigned int Type;
    
    	unsigned int Index;
    
    	struct TBoundary** ppBoundary;
    	unsigned int nBoundary;
    }TFamily;
    
    TFamily* AddFamilyArray(TFamily** pFamily, unsigned int* nFamilyArray){
    	TFamily* F = NULL, *tmp_Family=NULL;
    
    	if (*ppFamily == NULL){
    		tmp_Family = malloc(sizeof(TFamily));
    		if (tmp_Family == NULL){ return NULL; }else{ ppFamily = &tmp_Family; }
    	}
    	else{
    		tmp_Family = realloc(*ppFamily, (*nFamilyArray + 1)*sizeof(TFamily));
    		if (tmp_Family == NULL){ return NULL; }else{ ppFamily = &tmp_Family; }
    	}
    
             F = &tmp_Family[*nFamilyArray];
    	(*nFamilyArray)++;
    
    	F->Mesh.nNode = 0; /*Crash, wenn nFamilyArray == 2*/
    	F->Mesh.ppNode = NULL;
    
    	F->Mesh.nEdge = 0;
    	F->Mesh.ppEdge = NULL;
    
    	F->Mesh.nFace = 0;
    	F->Mesh.ppFace = NULL;
    
    	F->Mesh.nCell = 0;
    	F->Mesh.ppCell = NULL;
    
    	F->Index = 0;
    	F->Type = 0;
    	F->Name = NULL;
    	F->nBoundary = 0;
    	F->ppBoundary = NULL;
    
    	return F;
    }
    

    Die Funktion crashed beim zweiten Durchlauf was bedeutet, dass sie beim ersten mal - als realloc nicht ausgeführt wurde (if Abfrage) korrekt gearbeitet hat. Ist auch so - habe mal nen Breakpoint gesetzt - nach dem ersten Durchlauf ist alles korrekt - auch die Einträge.
    Beim zweiten Durchlauf verhindert die Runtime, dass ich F lesen kann - falscher Zeiger.

    Sieht jemand, woran das liegen kann?



  • 1. Warum verwendest du ungarische Notation? Das ist böse, böse, böse - gemeinsame Präfixe (am besten mit Unterstrich) bei Strukturen, OK, aber sonst reduziert das nur die Lesbarkeit.

    2. Du gewöhnst dir besser gar nicht erst an, nicht auf die Rückgabewerte von malloc und realloc zu prüfen. Sonst übersiehst du das an einer Stelle ("wird schon nicht so wichtig sein"), und dann hast du Probleme.

    3. Du bist mit deinen Pointertypen durcheinandergekommen, und bei diesem Pointer-Auf-Pointer-Chaos ist das auch kein Wunder.
    pFamily ist ein Pointer auf ein Array von Pointer, kein Array von Pointern auf Pointer. Sprich, vor der Zuweisung von F möchtest du erst den Wert (das Array) holen, indem du den Pointer dereferenzierst, dann greifst du über den Indexoperator auf das Array zu und holst dir dann die Adresse.

    F = &((*pFamily)[*nFamilyArray]);
    


  • Äh, Quatsch. pFamily ist ein Pointer auf ein Array von Objekten, nicht von Pointern. Sonst bräuchtest du ja nicht den Adressoperator hier, um auf das Objekt zuzugreifen, sondern könntest einfach den Wert im Pointer verwenden. Hirnfurz, das.



  • Nur mal so als Tipp:
    realloc kommt auch mit NULL beim ersten Parameter klar.
    Dann verhält es sich wie malloc .

    CJens schrieb:

    /*Crash, wenn nFamilyArray == 2*/

    Welchen Wert hat denn ppFamily in dem Fall?

    tmp_Family ist eine lokale Variable. Die Adresse davon aus der Funktion rauszugeben ist eine sehr sehr schlecht Idee.
    Aber wahrscheinlich willst du das auch nicht, sondern

    *ppFamily = tmp_Family;
    

    Wo kommt eigentlich das ppFamily her?



  • Bei großer Anzahl von Elementen wird jeweils 1x realloc sehr teuer, dann solltest du blockorientiert arbeiten um die Anzahl von reallocs zu minimieren.

    Du kannst hier die Kopiersemantik bei Funktionen ausnutzen, dir die temp. Variable sparen und dann nur mit dem Funktions-Return im aufrufenden Kontext weiterarbeiten:

    TFamily* addFamilyArray(TFamily* pFamily, size_t *n)
    {
      pFamily = realloc(pFamily, (*n+1) * sizeof*pFamily );
      if( pFamily )
      {
        memset( pFamily+ *n, 0, sizeof*pFamily );
        ++*n;
      }
      else
        assert(0);
    
      return pFamily;
    }
    
    TFamily *p = NULL;
      size_t n = 0;
    
      p = addFamilyArray(p,&n);
      ...
      p = addFamilyArray(p,&n);
      ...
    

  • Mod

    if( cond )
      {
        ...
      }
      else
        assert(0);
    

    Ich hoffe, das taucht so nicht in Produkivcode auf.


Anmelden zum Antworten