Forum: Mikrocontroller und Digitale Elektronik malloc, realloc und free


von rob (Gast)


Lesenswert?

Hi,

Angenommen ich reserviere einen Speicherbereich von 10 Bytes mithilfe 
der Funktion "malloc" und möchte ihn nach einer gewissen Zeit kürzen z.B 
auf 5 Bytes ohne dass die ersten 5 Bytes verloren.
Ist es möglich dass mit der Funktion "realloc" den Speicherbereich auf 5 
zu reduzieren wobei die oberen 5 Bytes wieder freigegeben werden und 
ohne dass die ersten 5 Bytes verändert / gelöscht werden?

Denn in meinem Programm wird zyklisch (alle 4 Sekunden) eine Struktur 
(Task) von insgesamt 25 Bytes im Speicherbereich (Warteschlange) 
hinzugefügt. Diese Struktur hat eine ID und wird von von einem 
Taskmanager bearbeitet und schlussendlich gelöscht, wenn der Task 
beendet wurde. Beim löschen des Tasks wird es eben mit der Funktion 
"realloc" realisiert doch nach ca. 30 erfolgreichen Betriebminuten gibt 
mit die realloc (Beim hinzufügen einer Struktur) einen NULL-Pointer 
zurück (-> Speicher voll bzw. kein Speicherbereich gefunden der gross 
genug ist).
Deshalb vermute ich, dass der Speicher mit "realloc" (Size < aktuelle 
Size) den überschüssigen Speicherbereich nicht freigibt.

Ich hoffe ihr könnt mir einige Ratschläge / Tipps geben.

Freundliche Grüsse

Rob

: Gesperrt durch User
von rob (Gast)


Lesenswert?

Hier noch die zwei relevanten Funktionen:

Hinzufügen eines Tasks:
1
BOOL AddNewTaskToList(
2
#if TASK_NAME_ENABLED
3
  char Name[TASK_NAME_SIZE],
4
#endif
5
  uint8_t ID, uint8_t Priority, void (*AddressOf)(tTask *ActTask), eRepeatCondition RepeatCondition)
6
{
7
  TaskList.nbrOfTasks++;
8
  if(GetNbrOfTasks() <= 1)
9
  {
10
    TaskList.Task = malloc(1*sizeof(tTask));
11
#if TASK_NAME_ENABLED
12
    strcpy(TaskList.Task->Name, Name);
13
#endif
14
    TaskList.Task->ID = ID;
15
    TaskList.Task->Priority = Priority;
16
    TaskList.Task->Tick = AddressOf;
17
    TaskList.Task->TaskRepeat = RepeatCondition;
18
    TaskList.Task->HasFinishedTask = FALSE;
19
    TaskList.Task->ElapsedTickTimeMs = 0;
20
  }  
21
  else
22
  {
23
    TaskList.Task = realloc(TaskList.Task, GetNbrOfTasks()*sizeof(tTask));
24
#if TASK_NAME_ENABLED
25
    strcpy(TaskList.Task[GetNbrOfTasks() - 1].Name, Name);
26
#endif
27
    TaskList.Task[GetNbrOfTasks() - 1].ID = ID;
28
    TaskList.Task[GetNbrOfTasks() - 1].Priority = Priority;
29
    TaskList.Task[GetNbrOfTasks() - 1].Tick = AddressOf;
30
    TaskList.Task[GetNbrOfTasks() - 1].TaskRepeat = RepeatCondition;
31
    TaskList.Task[GetNbrOfTasks() - 1].HasFinishedTask = FALSE;
32
    TaskList.Task[GetNbrOfTasks() - 1].ElapsedTickTimeMs = 0;  
33
  }
34
35
  if(TaskList.Task != NULL)  return TRUE;
36
  else return FALSE;
37
}

und löschen des Tasks:
1
void DeleteDoneTasks(void)
2
{  
3
  tTask *pFinishedTask;
4
  for(uint8_t i = 0; i < GetNbrOfTasks(); i++)
5
  {
6
    if(TaskList.Task[i].HasFinishedTask && TaskList.Task[i].TaskRepeat == noRepeat)
7
    {
8
      pFinishedTask = &TaskList.Task[i];
9
      *pFinishedTask = TaskList.Task[i + 1];
10
      for(uint8_t j = 1; j < GetNbrOfTasks() - (i+1);j++)
11
      {
12
        TaskList.Task[j] = TaskList.Task[j + 1];
13
      }
14
      TaskList.nbrOfTasks--;
15
      realloc(TaskList.Task, GetNbrOfTasks()*sizeof(tTask));  
16
    }
17
    else
18
    {
19
      if(TaskList.Task[i].HasFinishedTask)
20
      {
21
        TaskList.Task[i].HasFinishedTask = FALSE;
22
      }      
23
    }
24
  }
25
}

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

rob schrieb:
> Deshalb vermute ich, dass der Speicher mit "realloc" (Size < aktuelle
> Size) den überschüssigen Speicherbereich nicht freigibt.

Hast Du Dich schon mal mit Speicherfragmentierung beschäftigt?

von Rolf Magnus (Gast)


Lesenswert?

rob schrieb:
> Angenommen ich reserviere einen Speicherbereich von 10 Bytes mithilfe
> der Funktion "malloc" und möchte ihn nach einer gewissen Zeit kürzen z.B
> auf 5 Bytes ohne dass die ersten 5 Bytes verloren.
> Ist es möglich dass mit der Funktion "realloc" den Speicherbereich auf 5
> zu reduzieren wobei die oberen 5 Bytes wieder freigegeben werden und
> ohne dass die ersten 5 Bytes verändert / gelöscht werden?

Ja, das ist möglich. Ob realloc den Speicher wieder freigibt, hängt von 
dessen Implementation ab. Aber auch wenn es ihn freigibt, wird der freie 
Speicher ja nicht einfach in einen großen Eimer gekippt, aus dem man 
sich nachher bedienen kann. Du hast, wenn du deinen 10-Byte-Block auf 5 
Byte reduzierst, dann eben einen freien Block von 5 Bytes. Das nächste 
malloc(10) kann diesen nicht nutzen, da er zu klein ist. Irgendwann ist 
dann dein Speicher voll, obwohl noch viel Platz ist, weil du überall 
verteilt einzelne freie Bereiche hast, von denen aber jeder zu klein 
ist, um ein neues Objekt zu erzeugen. Das nennt sich 
Speicherfragmentierung.

von Karl H. (kbuchegg)


Lesenswert?

Mich wundert, dass dein Memory Manager nicht WO gibt

falsch:
1
     realloc(TaskList.Task, GetNbrOfTasks()*sizeof(tTask));


richtig:
1
     TaskList.Task = realloc(TaskList.Task, GetNbrOfTasks()*sizeof(tTask));


das deine erste Version nicht richtig sein kann, ersiehst du schon 
daran, dass TaskList.Task nicht per Pointer übergeben wird. realloc hat 
daher gar keine Chance, da den neuen Pointer Wert reinzuschreiben. D.h. 
dein realloc reallokiert immer mit demselben Pointer, selbst wenn der 
Speicher bereits längst freigegeben wurde. Das geht also nur solange 
gut, solange realloc den allokierten Speicher nicht verschieben muss. 
Was normalerweise nur dann sauber möglich ist, wenn ansonsten keine 
dynamische Allokierungen passieren. Wenn das aber das einzige ist, was 
dynamisch allokiert wird, sollte man sich fragen, wie sinnvoll eine 
dynamische Allokierung überhaupt ist.

An deiner Stelle würde ich mir auch überlegen, ob die Sicht der Dinge, 
das alles als 1 großes Array anzusehen, vernünftig ist. Je großer die zu 
allokierende Speichermenge ist, desto schwieriger wird es für die 
Allokierung einen geeigneten Speicherblock bei fragmentiertem Speicher 
zu besorgen.

von rob (Gast)


Lesenswert?

Rufus Τ. Firefly schrieb:
> rob schrieb:
>> Deshalb vermute ich, dass der Speicher mit "realloc" (Size < aktuelle
>> Size) den überschüssigen Speicherbereich nicht freigibt.
>
> Hast Du Dich schon mal mit Speicherfragmentierung beschäftigt?

Ehrlich gesagt habe ich mich damit nicht intensiv beschäftigt, ich weiss 
zwar schwammig was das ist, doch ich meine das hat beim AVR Xmega keinen 
Einfluss aufs RAM indem die Strukturen ja angelegt werden.
Ich werde mich jedenfalls damit beschäftigen, man lernt nie aus!

Aber ich glaube ich hab das Problem gefunden:
Falls ich den letzten aktiven Task lösche will die Delete funktion den 
Speicherbereich mit "realloc"(size !!0!!) "kürzen". Vielleicht gelingt 
das ja auch doch sobald wieder ein Task hinzugefügt wird, und wenn er 
der ERSTE ist, wird mit malloc, nicht realloc, einen neuen 
Speicherbereich definiert, obwohl der vorherige zwar mit realloc( size 0 
) gekürzt wurde, jedoch nicht komplett freigegeben wurde(Annahme).


so muss in der Delete Funktion beim löschen eines Tasks zwischen 
folgenden Situationen unterschieden werden:
Anzahl Tasks > 1          -> muss dann "realloc" verwenden
und
Anzahl Tasks <= 1         -> muss dann "free" verwenden.

Ich teste diese Korrektur mal, wär jedoch schön wenns jemand bestätigen 
könnte :)

Rob

von Karl H. (kbuchegg)


Lesenswert?

PS:
Deine DeleteDoneTasks Funktion sieht falsch aus.
Die j-Schleife kommt mir nicht koscher vor.

von Cyblord -. (cyblord)


Lesenswert?

rob schrieb:

> Ehrlich gesagt habe ich mich damit nicht intensiv beschäftigt, ich weiss
> zwar schwammig was das ist, doch ich meine das hat beim AVR Xmega keinen
> Einfluss aufs RAM indem die Strukturen ja angelegt werden.
Wie meinst du das denn? Natürlich hat es Einfluss und zwar in dem es 
deinen RAM fragmentiert. Und sehr bald darauf wird malloc dann eine 
false zurückliefern. Wehe dem der dann nicht das 6. Gebot beachtet ;-)

gruß cyblord

von Karl H. (kbuchegg)


Lesenswert?

rob schrieb:


> Aber ich glaube ich hab das Problem gefunden:
> Falls ich den letzten aktiven Task lösche will die Delete funktion den
> Speicherbereich mit "realloc"(size !!0!!) "kürzen".


Das passt schon.
Man darf realloc mit 0 aufrufen. Das wirkt dann wie ein free und du 
kriegst von realloc einen NULL Pointer zurück. Wenn du den auch noch in 
den Task Pointer schreiben würdest, wäre so gesehen die Sache geritzt.


Du musst schon nach den Regeln spielen. realloc wird IMMER nach dem 
Muster benutzt

   ptr = ralloc( ptr, new_size );

IMMER!
Egal ob realloc dann sein Bestes tut um Speicherverschiebungen zu 
vermeiden. Wenn du Annahmen darüber triffst, wie die Speicherverwaltung 
intern arbeitet, hast du gerade deine Seele an den Teufel verkauft.

von rob (Gast)


Lesenswert?

Hallo Karl Heinz Buchegger

Du hast wohl einen fatalen Fehler gefunden, vielen Dank.

Generell müssen immer ca 2-3 Strukturen à 25 Bytes allokiert sein, aber 
es muss auch möglich sein, dass eine "grosse" (10-20?) Anzahl in die 
"Warteschlange" gespeichert werden kann, welche dann Parallel abarbeitet 
wird.
Die Frage ob es sinnvoll ist das dynamisch zu regeln frag ich mich auch 
grad. Doch wenns so funktioniert wäre es doch nichts schlechtes? 
(Sicherlich besser als statisch?)

Grüsse Rob

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


Lesenswert?

Rolf Magnus schrieb:
> Du hast, wenn du deinen 10-Byte-Block auf 5
> Byte reduzierst, dann eben einen freien Block von 5 Bytes.

Real noch weniger: es geht ja noch Overhead für die Verwaltung der
Blöcke mit drauf, selbst bei einem kleinen Speichermodell (AVR) sind
das noch 2 Bytes.  Es bliebe also ein Block übrig, aus dem man 3
Bytes neu allozieren könnte.  Erst, wenn auch der ursprüngliche Block
wieder freigegeben ist, kann man beide freien Blöcke wieder zu einem
zusammenfügen, der wieder 10 Bytes hergeben würde (aber natürlich
nur, wenn der hintere Block nicht mittlerweile doch durch eine
Allokation von 3 Bytes noch belegt worden war).

Werden die restlichen Blöcke wirklich nie mehr freigegeben, dann
wird der Speicher ziemlich schnell fragmentieren auf diese Weise.

von Cyblord -. (cyblord)


Lesenswert?

rob schrieb:

> Die Frage ob es sinnvoll ist das dynamisch zu regeln frag ich mich auch
> grad. Doch wenns so funktioniert wäre es doch nichts schlechtes?
> (Sicherlich besser als statisch?)

Falls irgendwie möglich, ist statisch besser. Dann weißt du nämlich im 
vorraus wieviel Speicher du brauchst. Dynamischer Speicher ist auf 
kleinen Controllern (und der xmega gehlört da noch dazu) nicht so prall.

gruß cyblord

von Karl H. (kbuchegg)


Lesenswert?

rob schrieb:

> Generell müssen immer ca 2-3 Strukturen à 25 Bytes allokiert sein, aber
> es muss auch möglich sein, dass eine "grosse" (10-20?) Anzahl in die
> "Warteschlange" gespeichert werden kann, welche dann Parallel abarbeitet
> wird.
> Die Frage ob es sinnvoll ist das dynamisch zu regeln frag ich mich auch
> grad. Doch wenns so funktioniert wäre es doch nichts schlechtes?
> (Sicherlich besser als statisch?)

Das Problem:
Du hast ja nicht mehr Speicher zur Verfügung, nur weil du dynamisch 
allokierst. Wenn Ende der Fahnenstange ist, dann ist Ende.

Ob daher der Speicher brach liegt, bist du ihn mittels alloc allokierst, 
oder ob du genau denselben Speicher in ein vordimensioniertes Array 
steckst, kommt sich letzten Endes aufs gleiche raus - wenn das das 
einzige ist, was du dynamisch allokierst.

Einziger UNterschied: bei statischer Allokierung taucht der 
Speicherverbrauch in der COmpilerstatistik über den Speicherverbrauch 
auf und du kannst abschätzen bzw. entscheiden ob du mit dem Heap 
kollidieren wirst oder nicht.
Bei dynamischer Allokierung musst du ..... auf dein Glück vertrauen, 
dass genau letzteres nicht passiert.

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


Lesenswert?

Karl Heinz Buchegger schrieb:
> realloc wird IMMER nach dem
> Muster benutzt
>
>    ptr = realloc( ptr, new_size );

Naja.  Diese Form ist nur garantiert, wenn new_size wirklich kleiner
wird als die originale Größe.

Will man mit realloc einen Block vergrößern, muss man einen zweiten
Zeiger zu Hilfe nehmen:
1
  new_ptr = realloc(ptr, new_size);
2
  if (new_ptr == NULL) {
3
    throw_exception(OUT_OF_MEMORY);
4
    return ptr; // ptr zeigt noch auf den alten Bereich und ist nach
5
                // wie vor gültig; ggf. noch free() aufrufen!
6
  }
7
  ptr = new_ptr;

von Karl H. (kbuchegg)


Lesenswert?

Jörg Wunsch schrieb:

>
1
>   new_ptr = realloc(ptr, new_size);
2
>   if (new_ptr == NULL) {
3
>     throw_exception(OUT_OF_MEMORY);
4
>     return ptr; // ptr zeigt noch auf den alten Bereich und ist nach
5
>                 // wie vor gültig; ggf. noch free() aufrufen!
6
>   }
7
>   ptr = new_ptr;
8
>

Ja, ist mir auch gerade geschossen, als ich den Beitrag weggegeben habe, 
dass man von realloc ja auch einen NULL Pointer im Fehlerfall kriegt.
Danke, für die Klarstellung.

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


Lesenswert?

Karl Heinz Buchegger schrieb:
> Ob daher der Speicher brach liegt, bist du ihn mittels alloc allokierst,
> oder ob du genau denselben Speicher in ein vordimensioniertes Array
> steckst, kommt sich letzten Endes aufs gleiche raus - wenn das das
> einzige ist, was du dynamisch allokierst.

Hatten wir neulich schon mal.  Bei einer (zu) großen statischen
Allokierung riskiert man immer, dass man mit dem Stack kollidiert,
ohne dass das noch jemand zur Laufzeit bemerkt.  Bei einer dynamischen
Zuweisung könnte es zumindest prinzipiell noch bemerkt werden, dass
der Abstand zum Stack jetzt zu knapp wird.

von Cyblord -. (cyblord)


Lesenswert?

Wird das denn von malloc und co. auch tatächlich bemerkt? Ist das 
entsprechend implementiert?

von Karl H. (kbuchegg)


Lesenswert?

Jörg Wunsch schrieb:

> Hatten wir neulich schon mal.  Bei einer (zu) großen statischen
> Allokierung riskiert man immer, dass man mit dem Stack kollidiert,
> ohne dass das noch jemand zur Laufzeit bemerkt.

Deswegen gibts da ja auch die Speicherstatistik, mit der man zumindest 
ein Gefühl dafür kriegt, wieviel Platz noch für den Stack übrig ist. Mit 
der Kentniss des Programmaufbaus kann man dann wenigstens Daumen mal Pi 
abschätzen, ob das reichen könnte oder nicht.

>  Bei einer dynamischen
> Zuweisung könnte es zumindest prinzipiell noch bemerkt werden, dass
> der Abstand zum Stack jetzt zu knapp wird.

Es sei denn du hast den Fall, dass dir der Stack in den allokierten 
Speicher rein wächst. Dann kann alles mögliche passieren, wenn Daten 
geschrieben werden und danach ein Rücksprung aus einer Funktion 
passiert, deren Returnadresse nicht mehr stimmt.

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


Lesenswert?

cyblord ---- schrieb:
> Wird das denn von malloc und co. auch tatächlich bemerkt? Ist das
> entsprechend implementiert?

In der avr-libc ist es so implementiert.  Die Reserve, die zwischen
Stack und Heap noch frei bleiben muss beim Allozieren, ist durch
den Nutzer einstellbar (Variable __malloc_margin, default 32 Bytes).

Karl Heinz Buchegger schrieb:
> Deswegen gibts da ja auch die Speicherstatistik, mit der man zumindest
> ein Gefühl dafür kriegt, wieviel Platz noch für den Stack übrig ist. Mit
> der Kentniss des Programmaufbaus kann man dann wenigstens Daumen mal Pi
> abschätzen, ob das reichen könnte oder nicht.

Eine vergleichbare Abschätzung kann man aber auch in einem dynamischen
Projekt machen, nur eben nicht gleich nach dem Compilieren ;), sondern
während der Entwicklungsphase, nachdem die Applikation einen typischen
Zeitraum bereits lief.  Es ist hie wie da ein "Bauchgefühl", das man
da ansetzt, aber mehr kann man den Umständen entsprechend sowieso nie
tun: wenn der Speicher alle ist, ist einfach Schluss.  Die Appliakation
kann nicht noch schnell beim Controller-Hersteller welchen nachkaufen
gehen. ;-)

von Albert (Gast)


Lesenswert?

Hallo Ich muss das Thema wieder aufrollen. Ich bekomm in meinem Code 
irgendwo eine Zugriffsverletzung. Ich weiß aber nicht wo. Hoffe ihr 
könnt mir dabei helfen.

H-File:
typedef struct {
  int temp;
  int druck;
  int day;
  int month;
  int year;

}weatherdata;

void appendWeatherData(weatherdata * data,int *sizeOfData);

C-File

void appendWeatherData(weatherdata *data, int *sizeOfData)
{
  static int sizeOfDataSpace = 10;

  if (sizeOfDataSpace <= *sizeOfData) {

    if( (realloc(data, sizeof(weatherdata) * 10))!=NULL);
    {
      sizeOfDataSpace += 10;
    }




  }

  //irgendwas reinschreiben
  if (*sizeOfData < sizeOfDataSpace) {



    data[*sizeOfData].day = *sizeOfData;
    data[*sizeOfData].druck = *sizeOfData;
    data[*sizeOfData].month = *sizeOfData;
    data[*sizeOfData].temp = *sizeOfData;
    data[*sizeOfData].year = *sizeOfData;

    *sizeOfData = *sizeOfData + 1;

  }



}
Main-File:
int main()
{
  int size = 0;
  weatherdata *data;

  data = (weatherdata*)calloc(10 ,sizeof(weatherdata));

  for (int i = 0; i < 20; i++)
  {
    appendWeatherData(data, &size);


  }

  for (int i = 0; i < size-1; i++)
  {
    if (data != NULL) {
      printf("Daten: %d -->%d %d %d Temp:%d Druck%d \n",i+1 
,data[i].day,data[i].month,data[i].year, data[i].temp, data[i].druck);
    }




  }

  if (data != NULL) {

    free(data);
  }



    return 0;
}

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Albert schrieb:
> Hallo Ich muss das Thema wieder aufrollen.

Mach das bitte in einem neuen, eigenen Thread anstelle einen fünf Jahre 
alten, in dem es um andere Dinge geht.

Oh, und wenn Du das machst, nutze die [ c ] [ /c ] -Tags, die das Forum 
anbietet, damit man Deinen Quelltext vernünftig lesen kann.

Dieser Beitrag ist gesperrt und kann nicht beantwortet werden.