Forum: Compiler & IDEs hilfe zu malloc und free bei verketter liste


von husten (Gast)


Lesenswert?

ich brauch mal eine hilfe zu malloc( calloc )  und free

MCU ist ein Cortex M3


es soll eine einfach verkettete liste sein ..
dazu wird bei bedarf ein elemet erstellt
später irgendwann wieder gelöscht.

Frage an die PROFIS ..
geht das "FREE(" hier so ?

das calloc funktioniert hier prima ..

1
__attribute__((packed)) struct data_struct{
2
  struct data_struct  *next;
3
  uint32_t       data[12];
4
};
5
6
struct data_struct*   data_table = NULL;
7
8
void data_remove( struct data_struct* block ){
9
  struct data_struct* tmp;
10
  if(data_table == block) {
11
    tmp = block->next;
12
    memset( block ,0, sizeof(struct data_struct) );
13
    free(block);
14
    data_table = tmp;
15
  }else{
16
    for(tmp = data_table; tmp != NULL; tmp = tmp->next) {
17
      if(tmp->next == block) {
18
        tmp->next = block->next;
19
        memset( block ,0, sizeof(struct data_struct) );
20
        free( block );
21
        break;
22
      }
23
    }
24
  }
25
}
26
27
struct data_struct* data_new( void ){
28
  struct data_struct *new_block;
29
30
  new_block = calloc( sizeof(struct data_struct) , sizeof(char) );
31
  if( new_block == NULL ){
32
    return NULL;
33
  }
34
  if(data_table == NULL) {
35
    new_block->next = NULL;
36
  }else{
37
    new_block->next = data_table;
38
  }
39
  data_table = new_block;  // new element to first position in list
40
  return new_block;
41
}
42
43
int main(void){
44
        struct data_struct*   data;
45
  SystemInit();
46
47
  data = data_new();
48
  data->data[0] = 5 ;
49
  data_remove( data );
50
51
        for(;;);
52
}

von Jay (Gast)


Lesenswert?

Das ist zu kompliziert gemacht, bzw. zum Teil sinnloser Code. Nur ein 
Beispiel:
1
  if(data_table == NULL) {
2
    new_block->next = NULL;
3
  }else{
4
    new_block->next = data_table;
5
  }

kann man zu einer Zeile eindampfen:
1
 new_block->next = data_table;

So geht das dann weite. calloc() macht keinen Sinn. Die Sonderbehandlung 
des ersten Blocks beim remove macht keinen Sinn, usw.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Blöcke vor dem free() ausnullen ist, sofern es sich nicht gerade um
sensitive Daten (Passwörter etc.) handelt, auch eher überflüssig.

Aber im Prinzip müsste das mit dem free() so passen.

von husten (Gast)


Lesenswert?

Jay schrieb:
> Das ist zu kompliziert gemacht, bzw. zum Teil sinnloser Code. Nur ein
> Beispiel:
>   if(data_table == NULL) {
>     new_block->next = NULL;
>   }else{
>     new_block->next = data_table;
>   }
>
> kann man zu einer Zeile eindampfen:
>  new_block->next = data_table;
>
> So geht das dann weite. calloc() macht keinen Sinn. Die Sonderbehandlung
> des ersten Blocks beim remove macht keinen Sinn, usw.

ja optimieren kann man dies sicher ..
nur gehts ertsmal grob um die funktion ...

calloc weil ich alles "0" haben will
sonst müsste ich malloc und memset machen

diese sonderbehaldlung keinen sinn ?
es gibt bei dieser liste keinen listenkopf
die fängt eben mit einem pointer auf NULL an und endet genau in diesem
mal schauenw as er dazu sagt wenn ich das auslasse

von Peter D. (peda)


Lesenswert?

Auf einem MC kann man in der Regel keine Anwendung schließen, wenn der 
Speicher knapp wird.
Daher reserviert man einfach fest eine sinnvolle Größe an RAM für die 
Liste und gut.

Auch hat man oft auf einem MC keinen Garbage-Collector. D.h. wenn man 
extensiv malloc und free benutzt, kann es passieren, daß irgendwann der 
RAM so sehr fragmentiert ist, daß malloc fehlschlägt.

von husten (Gast)


Lesenswert?

Jörg Wunsch schrieb:
> Blöcke vor dem free() ausnullen ist, sofern es sich nicht gerade um
> sensitive Daten (Passwörter etc.) handelt, auch eher überflüssig.
>
> Aber im Prinzip müsste das mit dem free() so passen.

OK
dachte das sollte man lieber nullen ...

das free passt soweit ..
habe meinen fehler gefunden.

im timer war nach dem remove die liste natürlich nicht mehr aktuell und 
der ->next zeigte nun ggf ins nichts


danke

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Peter Dannegger schrieb:
> Auf einem MC kann man in der Regel keine Anwendung schließen, wenn der
> Speicher knapp wird.

Doch.  Reboot. :-)

> Daher reserviert man einfach fest eine sinnvolle Größe an RAM für die
> Liste und gut.

Welche Größe ist denn sinnvoll, wenn du sie vorher nicht kennst?

Was passiert, wenn dann mehr Daten vorliegen, als diese „sinnvolle“
Größe handhaben kann?

Wenn man ein dynamisches Problem hat, hat man immer die Po-Karte
gezogen, wenn mehr Daten anfallen, als man gerade Speicher hat.
Eine „Exit-Strategie“ benötigt man in diesem Falle immer, egal, ob
man nun das verfemte malloc() benutzt oder irgendwas Präalloziertes.

Wenn man kein dynamisches Problem hat, braucht man auch keine
Klimmzüge mit Listen und dergleichen anstellen.

> Auch hat man oft auf einem MC keinen Garbage-Collector. D.h. wenn man
> extensiv malloc und free benutzt, kann es passieren, daß irgendwann der
> RAM so sehr fragmentiert ist, daß malloc fehlschlägt.

Da er nur gleich große Strukturen alloziert, passiert das nicht: in
einen freigegebenen Bereich muss dann immer wieder eine neue Struktur
dieser Größe reinpassen, alles andere wäre unlogisch.

Bei ungleich großen Datenelementen kommt es darauf an, inwiefern deren
Größe beim Datenanfall gut genug statistisch verteilt ist.  Ab einer
bestimmten Speicherreserve (ähnlich der 10-%-Reserve beim Berkeley
UFS [Unix FileSystem]) dürfte ein derartiges System langzeitstabil
werden, d. h. die Fragmentierung erreicht irgendwann einfach einen
Maximalwert.

Hängt aber natürlich alles auch von der malloc()-Implementierung
selbst ab.  Keine Ahnung, was da bei der Newlib dabei ist.

Auch ist natürlich „Cortex M3“ nur eine sehr grobe Hausnummer.  Da
kann von wenigen Kilobyte RAM bis zu mehreren Megabyte alles dran
hängen.  Es gab Zeiten, da liefen mit 2 x 64 KiB RAM (sogar noch
etwas weniger wegen der System IO Register Page) ganze UNIX-Systeme …

: Bearbeitet durch Moderator
von Karl H. (kbuchegg)


Lesenswert?

husten schrieb:

> OK
> dachte das sollte man lieber nullen ...

Zu Debug Zwecken ist das sicher nicht schlecht.
Wobei es manchmal besser ist, ein anderes Muster zu benutzen. 0 kommt in 
den Daten schon recht häufig vor. Alles mit 0xFF vollzukleistern wäre zb 
eine Möglichkeit. Stösst du während der Entwicklung je auf einen Block, 
der lauter 0xFF enthält, dann weißt du, dass du einen ungültigen Pointer 
im System hast und daher noch irgendwo mindestens 1 Fehler im Programm 
sein muss.

> diese sonderbehaldlung keinen sinn ?

Die Sonderbehandlung im remove ist schon ok. Solange du einen 
Root-Pointer hast und nicht ein komplettes Objekt als Root-Objekt 
vorsiehst geht das nicht anders.

Allerdings solltest du deinen Code trotzdem schnell streamlinen. Gerade 
bei derartigen Funktionalitäten verliert man schnell den Überblick, wenn 
man es zu lange hinauszögert. Und dann läuft man Fehlern nach, die man 
nicht sieht, weil man sich in den Sonderfällen verhaspelt, die 
eigentlich gar nicht notwendig sind. Gerade bei dynamischen 
Datenstrukturen ist sauberer und übersichtlicher Code sehr wichtig.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Tip: bei dynamischen Listen benötigt man auch oft eine 'Previous' 
Funktion, die zu einem Node seinen Vorgänger raussucht. Deine Remove 
Funktion basiert genau darauf. Machst du dir eine entsprechende Funktion 
dann vereinfacht sich die Funktion zu
1
void data_remove( struct data_struct* block ){
2
3
  if( data_table == block )
4
    data_table = block->next;
5
  else {
6
    struct data_struct* previous = data_previous( block );
7
    if( previous )
8
      previous->next = block->next;
9
  }
10
11
#ifdef DEBUG
12
  memset( block, MAGIC_PATTERN_BYTE, sizeof(struct data_struct) );
13
#endif
14
  free( block );
15
}

und das ist schon wieder leichter zu überblicken bzw. zu verstehen. 
Insbesonder letzteres, die leichte Erfassbarkeit beim Lesen eines Codes, 
ist ein wichtiges Kriterium in der Entwicklung. Je besser lesbar du dir 
den Code machst, umso weniger dumme Fehler machst du.

: Bearbeitet durch User
von Amateur (Gast)


Lesenswert?

Auch wenn der Speicher theoretisch reichen sollte:
Malloc und free sind bei geringem Gesamtspeicher problematisch.
Vor allem dann, wenn unterschiedlich große Häppchen abgebissen werden.

von husten (Gast)


Lesenswert?

hi

Danke erstmal für die gute erklärung

Karl Heinz schrieb:
> Allerdings solltest du deinen Code trotzdem schnell streamlinen.

das ist mein problem...
ich bekomm das nicht so schick hin.

Karl Heinz schrieb:
> Tip: bei dynamischen Listen benötigt man auch oft eine 'Previous'
> Funktion, die zu einem Node seinen Vorgänger raussucht.

stimmt.
Vielen dank werde das so übernehmen :-)

von flobber (Gast)


Lesenswert?

Die "übliche" Methode, ein Element aus einer einfach verketteten Liste 
zu löschen:
1
void data_remove(struct data *block)
2
{
3
    struct data *p, **pp;
4
5
    for (pp = &data_table; (p = *pp) != NULL; pp = &p->next)
6
        if (p == block)
7
        {
8
            *pp = p->next;
9
            free(p);
10
            break;
11
        }
12
}

Warum das funktioniert, solltest du zu Übungszwecken selbst 
herausknobeln ;-)

von husten (Gast)


Lesenswert?

flobber schrieb:
> Warum das funktioniert, solltest du zu Übungszwecken selbst
> herausknobeln ;-)


hi

danke dafür.
aber ich hab so meine problemchen mit **pp ...
ich habs jetzt probiert  .. und ja es geht ...

struct data *p, **pp;
*p is ein pointer
**pp ein pointer auf einen pointer

ich komm mit verdröselten for() nicht klar
for (pp = &data_table; (p = *pp) != NULL; pp = &p->next)


pp ist ein pointer auf den anfang
*pp ist ein pointer auf den ->next

insgesammt versteh ich es nicht wirklich ...

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

husten schrieb:
> ich komm mit verdröselten for() nicht klar

Immer dran denken, dass man ein for() auch als while() schreiben
kann:
1
for (x; y; z) {
2
  ...
3
}
4
5
=>
6
7
x;
8
while (y) {
9
  ...
10
  z;
11
}

(Es gibt geringfügige Unterschiede zwischen beiden, wenn man ein
continue macht, aber die spielen hier keine Rolle.)

von Karl H. (kbuchegg)


Lesenswert?

husten schrieb:

> *pp ist ein pointer auf den ->next

wobei du das mit dem 'next' unter den Tisch kehren solltest.
*pp ist einfach die Adresse jenes Speicherortes, an der der Pointer zu 
finden ist, der auf diesen Knoten zeigt.

Denn

Das können ja 2 verschiedene Dinge sind.
Im NOrmalfall ist es der next Pointer vom Vorgängerknoten.
Aber!
Beim ersten Knoten in der Liste gibt es keinen Vorgängerknoten, sondern 
in diesem Fall steht diese Adresse ja in data_table drinne.

Mit dem 2-Stern Pointer spielt das aber keine Rolle mehr. Denn dort 
steht ja die Adresse drinn, wo der Pointer auf diesen Knoten gespeichert 
ist. Und das kann natürlich auch genau dieses data_table sein.

D.h. mit dem 2-Stern Pointer hast du den Sonderfall des ersten Knotens 
bzw. des Root-Pointers umschifft. Das ist dann kein Sonderfall mehr.

von flobber (Gast)


Lesenswert?

> aber ich hab so meine problemchen mit **pp ...

Nicht durch die vielen Sternchen verwirren lassen ;-)  Wie du schon 
erkannt hast, ist es ein Pointer.  Auf was für einen Typ der Pointer 
zeigt, ist erstmal egal.  Schau lieber, wohin er zeigt - am Anfang und 
nach jedem Iterationsschritt.  Ein Blatt Papier kann da evtl hilfreich 
sein :-)

von flobber (Gast)


Lesenswert?

Schade, jetzt hat KH fast alles erklärt - mMn lernt man viel intensiver, 
wenn man sich so etwas selbst herausknobelt.

von Karl H. (kbuchegg)


Lesenswert?

flobber schrieb:

> Ein Blatt Papier kann da evtl hilfreich
> sein :-)

Das unterstütze ich ungemein.
Gerade bei dymaischen Datenstrukturen kann einem mitzeichnen der 
Operationen regelrecht die Augen öffnen.

von huhu (Gast)


Lesenswert?

Jörg Wunsch schrieb:
> Immer dran denken, dass man ein for() auch als while() schreiben
> kann:


OK  ds die beiden  grob das selbe machen war mir klar ...
aber SO hab ich das noch nicht betrachtet

Vielen Dank

Karl Heinz schrieb:
> Das können ja 2 verschiedene Dinge sind.
> Im NOrmalfall ist es der next Pointer vom Vorgängerknoten.
> Aber!
> Beim ersten Knoten in der Liste gibt es keinen Vorgängerknoten, sondern
> in diesem Fall steht diese Adresse ja in data_table drinne.
>
> Mit dem 2-Stern Pointer spielt das aber keine Rolle mehr. Denn dort
> steht ja die Adresse drinn, wo der Pointer auf diesen Knoten gespeichert
> ist. Und das kann natürlich auch genau dieses data_table sein.
>
> D.h. mit dem 2-Stern Pointer hast du den Sonderfall des ersten Knotens
> bzw. des Root-Pointers umschifft. Das ist dann kein Sonderfall mehr.

stimmt
beim steppen ist der anfangswert die adresse der tabelle
dann wird die adresse immer durchgeschoben
also vom aktuellen und dem vorgänger

flobber schrieb:
> Nicht durch die vielen Sternchen verwirren lassen ;-)  Wie du schon
> erkannt hast, ist es ein Pointer.  Auf was für einen Typ der Pointer
> zeigt, ist erstmal egal.  Schau lieber, wohin er zeigt - am Anfang und
> nach jedem Iterationsschritt.  Ein Blatt Papier kann da evtl hilfreich
> sein :-)

jaaa  die ** verwirren mich ^^



naja wieder etwas gelernt :-)
vielen dank nochmal an alle beteiligten

pointer und ihre eigenheiten ...
ich mag ja funktionszeger und so ..
aber das ** und solche konstrukte bereiten mir noch kopfschmerzen
hab mir aber schon diverse sachen parat gelegt um das mal weiter zu 
testen

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.