Forum: Mikrocontroller und Digitale Elektronik [c] dynamische Speicherverwaltung mit malloc


von Am2302 (Gast)


Lesenswert?

Hallo,

ich hätte ein Frage zu folgendem Code 
(http://openbook.rheinwerk-verlag.de/c_von_a_bis_z/014_c_dyn_speicherverwaltung_007.htm)
1
/* dyn_array3.c */
2
#include <stdio.h>
3
#include <string.h>
4
#include <stdlib.h>
5
6
int main(void) {
7
   int *value,*temp;
8
   int i=0, more;
9
   int size, merker = 0;
10
11
   printf("Wie viele Werte benötigen Sie : ");
12
   scanf("%d", &size);
13
   value = (int *)malloc(size*sizeof(int));
14
   if(NULL == value) {
15
      printf("Fehler bei malloc...!! n");
16
      return EXIT_FAILURE;
17
   }
18
   do {
19
      while(merker < size) {
20
         printf("Wert für value[%d] eingeben : ",merker);
21
         scanf("%d",&value[merker]);
22
         merker++;
23
      }
24
      printf("Neuen Platz reservieren (0=Ende) : ");
25
      scanf("%d",&more);
26
      temp = malloc(size*sizeof(int));
27
      if(NULL == temp) {
28
         printf("Kann keinen Speicher mehr reservieren!\n");
29
         return EXIT_FAILURE;
30
      }
31
      for(i=0; i<size; i++)
32
         temp[i]=value[i];
33
      size+=more;
34
      value = malloc(size * sizeof(int));
35
      if(NULL == value) {
36
         printf("Kann keinen Speicher mehr reservieren!\n");
37
         return EXIT_SUCCESS;
38
      }
39
      for(i=0; i<size; i++)
40
         value[i]=temp[i];
41
   }while(more!=0);
42
   printf("Hier Ihre Werte\n");
43
   for(i=0; i<size; i++)
44
      printf("value[%d] = %d\n" ,i ,value[i]);
45
   return EXIT_SUCCESS;
46
}


In der Zeile value = malloc(size * sizeof(int)); (nach size+more) wird 
neuer Speicher angefordert. Die Werte aus value wurden in temp 
zwischengespeichert. value zeigt doch aber schon auf einen 
Speicherbereich. Müsste ich diesen nicht vorher freigeben??

D.h.
1
      size+=more;
2
      free(value);
3
      value = malloc(size * sizeof(int));

Temp muss auch noch freigeben werden, aber da ist die Sachlage klar.

Vielen Dank.

von B. S. (bestucki)


Lesenswert?

Am2302 schrieb:
> Müsste ich diesen nicht vorher freigeben??

Ja, ausser du willst ein Speicherleck.

Am2302 schrieb:
> value = (int *)malloc(size*sizeof(int));

Den Cast nach int * sollte man auch weglassen, der ist unnötig. 
Ausserdem bevorzuge ich folgende Schreibweise:
1
int * Var = malloc(Size * sizeof(*Var));
So kann sich der Datentyp von Var ändern, ohne dass ich alle sizeof in 
den malloc-Aufrufe anpassen muss.

Im Allgemeinen sieht das Programm eher unübersichtlich aus. Ich würde da 
einige Dinge in Funktionen verpacken. Zudem noch Klammern um alle 
Schleifen und Variablen erst Deklarieren, wenn sie benötigt werden 
anstatt alle am Anfang der Funktion. Die Funktion scanf ist auch noch 
mal ein Thema für sich, ich würde diese nicht verwenden.

Am2302 schrieb:
> 
(http://openbook.rheinwerk-verlag.de/c_von_a_bis_z/014_c_dyn_speicherverwaltung_007.htm)

Ich rate von diesem Lehrwerk ab. Ich habe Teile des Buches gelesen und 
da sind teilweise üble Fehler drin. Ausserdem ist der Programmierstil, 
wie man bei diesem Beispiel gut sieht, eher schlecht als recht und es 
werden viele wichtige Details ignoriert, da es "normalerweise" auch so 
funktioniert.

: Bearbeitet durch User
von Markus M. (adrock)


Lesenswert?

Ja, das müsste rein.

Allerdings ist der ganze Konstrukt so etwas ungünstig.

Du hast doch schon am Anfang abgefragt, wieviele Werte benötigt werden. 
Danach soll dennoch erweitert werden können?

Naja, wie auch immer, eigentlich vermeidet man so ein Konstrukt, da 
dadurch der Speicher fragmentiert wird. Aber wenn es denn sein muss, 
würde ich es in dieser Art machen:

temp = malloc(newsize*sizeof(int));

for(i=0; i<size; i++) {
 temp[i] = value[i];
}

free(value);
value = temp;
size = newsize;

Dadurch sparst Du einen Kopiervorgang und ein malloc/free Pärchen.

EDIT: Wenn diese Code aus einem Lehrbuch ist, solltest Du ein anderes 
nehmen. So wird das niemand programmieren in der Praxis.

: Bearbeitet durch User
von Am2302 (Gast)


Lesenswert?

Markus M. schrieb:
> EDIT: Wenn diese Code aus einem Lehrbuch ist, solltest Du ein anderes
> nehmen. So wird das niemand programmieren in der Praxis.

Cool. Danke für die Antworten.
Der Speicher wird fragmentiert. Verstanden. Könntest du noch anreißen 
wie man es richtig macht?
Eine Empfehlung für ein C Buch nehme ich auch gerne. :-)
Einen schönen Abend.

von B. S. (bestucki)


Lesenswert?

Am2302 schrieb:
> Könntest du noch anreißen wie man es richtig macht?

Den Anfang hat Markus bereits geliefert, damit ersparst du dir unnötige 
Kopiererei oder du verwendest statt malloc realloc (das erste Mal 
benötigst du malloc), dann werden deine Daten automatisch kopiert. 
Ausserdem gleich noch scanf durch fgets ersetzen. Siehe z.B. hier 
(Anfangs gehts um was anderes, einfach weiterlesen):
Beitrag "Wann Tastaturpuffer mit fflush(stdin); löschen?"

Am2302 schrieb:
> Eine Empfehlung für ein C Buch nehme ich auch gerne. :-)

The C Programming Language von K&R, 2. Auflage. Behandelt zwar noch das 
alte C89, aber die späteren Erweiterungen sind im Internet schnell 
recherchiert, es sind ja nicht so viele.

Als Nachschlagewerk für die Standardbibliothek empfehle ich 
http://www.cplusplus.com/reference/clibrary/
Ist zwar eine Referenz für C++, aber die C Header sind auch mit drin.

von Am2302 (Gast)


Lesenswert?

Markus M. schrieb:
> temp = malloc(newsize*sizeof(int));

Müsste es hier nicht sowieso int* heißen ...

be s. schrieb:
> Den Anfang hat Markus bereits geliefert, damit ersparst du dir unnötige
> Kopiererei oder du verwendest statt malloc realloc (das erste Mal
> benötigst du malloc), dann werden deine Daten automatisch kopiert.

Okay. Danke. realloc gibts bei meinem verwendeten Betriebssystem nicht. 
Aber der scheint ja auch nur kopieren und somit den Speicher zu 
fragmentieren.
Ich frug nur, da Markus andeutete es geht auch (noch) besser.

be s. schrieb:
> Ausserdem gleich noch scanf durch fgets ersetzen. Siehe z.B. hier
> (Anfangs gehts um was anderes, einfach weiterlesen):
> Beitrag "Wann Tastaturpuffer mit fflush(stdin); löschen?"

Das war nur in dem angegebenen Beispiel und spielt auf meinem µC keine 
Rolle :-) Aber trotzdem interessant.

be s. schrieb:
> The C Programming Language von K&R, 2. Auflage. Behandelt zwar noch das
> alte C89, aber die späteren Erweiterungen sind im Internet schnell
> recherchiert, es sind ja nicht so viele.
>
> Als Nachschlagewerk für die Standardbibliothek empfehle ich
> http://www.cplusplus.com/reference/clibrary/
> Ist zwar eine Referenz für C++, aber die C Header sind auch mit drin.

Danke, ich glaub das besorge ich mir mal ...


Viele Grüße und noch mals Dank an euch!

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

be s. schrieb:
> das erste Mal benötigst du malloc

Nö, realloc kann auch für das erste Mal verwendet werden. Ist der 
übergebene Pointer ein NULL-Pointer, verhält sich realloc wie 
malloc.
1
ptr = realloc(NULL, newsize);


Beim Gebrauch von realloc ist es ratsam, nicht so vorzugehen:
1
ptr = realloc(ptr, newsize);

denn dann verwaist im Fehlerfall der bislang angeforderte Speicher, da 
der Pointer verworfen wird.

Daher ist immer erst der Rückgabwert auszuwerten, bevor der 
ursprüngliche Pointer überschrieben wird:
1
newptr = realloc(ptr, newsize);
2
3
if (newptr)
4
{
5
  ptr = newptr;
6
}
7
else
8
{
9
  // Fehlerbehandlung
10
}

von Karl H. (kbuchegg)


Lesenswert?

da ist ausserdem in den Originalroutinen noch ein übler Fehler drinnen
1
      temp = malloc(size*sizeof(int));
2
      if(NULL == temp) {
3
         printf("Kann keinen Speicher mehr reservieren!\n");
4
         return EXIT_FAILURE;
5
      }
6
      for(i=0; i<size; i++)
7
         temp[i]=value[i];
8
      size+=more;
9
      value = malloc(size * sizeof(int));
10
      if(NULL == value) {
11
         printf("Kann keinen Speicher mehr reservieren!\n");
12
         return EXIT_SUCCESS;
13
      }
14
      for(i=0; i<size; i++)
15
         value[i]=temp[i];

und hier zum Schluss wird auf Werte von temp zugegriffen, die überhaupt 
nicht existieren.

size ist zu diesem Zeitpunkt schon erhöht worden, value zeigt auch 
korrekt auf ein mit dieser Größe allokiertes Array. Aber temp nicht. Das 
Array, auf welches temp zeigt hat noch die vorhergehende size.

Man kann es nicht oft genug betonen. "Array out of bounds" Zugriffe sind 
die häufigsten und gleichzeitig mit die schlimmsten Fehler in C 
Programmen. Hier heisst es sorgfältig arbeiten!

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Am2302 schrieb:
> Markus M. schrieb:
>> temp = malloc(newsize*sizeof(int));
>
> Müsste es hier nicht sowieso int* heißen ...

Nein. Du willst ja nicht ein Array von Pointern allokieren.
Du willst ein Array von int allokieren. Dort sollen ja Integer drinn 
gespeichert werden.


> Okay. Danke. realloc gibts bei meinem verwendeten Betriebssystem nicht.
> Aber der scheint ja auch nur kopieren und somit den Speicher zu
> fragmentieren.

um Speicherfragmentierung kommst du normalerweise sowieso nicht herum. 
Egal ob du mit free freigibst oder nicht, egal ob du mittels realloc 
vergrößerst oder nicht. Wenn du allokierten Speicher nicht mittels free 
frei gibst, dann hast du ein sog. Speicherloch. Im Englischen sagt man 
auch, dein Programm 'leaked' (es leckt).

Speicherfragmentierung ist etwas anderes. Unter Speicherfragmentierung 
versteht man die Verwendung des Speichers, so dass zwischen den 
allokierten Speicherbereichen Lücken entstehen, die klein genug sind, so 
dass man mit ihnen nichts mehr anfangen kann. Dein Programm hat zwar 
dann rein rechnerisch zum Beispiel noch 2k frei, aber eben nicht in 1 
Stück, sondern in Form von 50 jeweils 4 Byte grossen Blöcken. Rein 
rechnerisch könntest du in den noch 2K verbliebenem Speicher also noch 
problemlos ein 1K grosses Array unterbringen. Praktisch kannst du das 
aber nicht, weil du das benötigte 1K nicht in einem Stück im Speicher 
frei hast.

Stell dir die Situation vor:
Das sei dein Speicher von 2000 Byte
1
+-------------------------------------------------+
2
|                                                 |
3
+-------------------------------------------------+
In diesem Speicher allokierst du 500 Bytes
1
+-------------------------------------------------+
2
|#############                                    |
3
+-------------------------------------------------+
Danach allokierst du 900 Bytes
1
+-------------------------------------------------+
2
|#############**********************              |
3
+-------------------------------------------------+
und gibst die erste Speicherallokierung wieder frei
1
+-------------------------------------------------+
2
|             **********************              |
3
+-------------------------------------------------+
rein rechnerisch hast du von deinen 2000 Bytes nur 900 Bytes verbraucht, 
hast also 2000 - 900 gleich 1100 Bytes noch frei. In der Grafik siehst 
du auch, dass links und rechts von deiner Allokierung noch freier Platz 
ist. Trotzdem bringst du in diesem 1100 Bytes freiem Speicher keine 
weitere Anforderung mit 800 Bytes unter. Denn die Lücke links vom 
allokierten ist 500 Bytes gross und damit kleiner als die geforderten 
800 und die Lücke rechts von der Allokierung ist 600 Bytes gross und 
damit ebenfalls zu klein. Die Speicheranforderung scheitert, weil dein 
Speicher fragmentiert ist.

Die einzige Art das zu vermeiden besteht in C darin, keine dynamischen 
Speicheranforderungen zu machen. Das hört sich jetzt schlimmer an als es 
ist. Sobald du hinreichend viel Speicher generell zur Verfügung hast, 
verschwindet das Problem immer mehr. Aber auf einem kleinen µC mit nur 
sehr wenig Speicher ist das ein ernstes Problem. Dort wird es so gelöst, 
dass man keine dynamischen Anforderungen macht. Habe ich den Speicher 
und ich weiss dass Array1 500 Bytes benötigt, Array 2 braucht 900 Bytes 
und Array 3 benötigt 800 Bytes UND weiss ich zusätzlich, dass Array1 und 
Array 3 niemals zur selben Zeit benötigt werden, dann lege ich mir 
statisch ein Array mit 800 Bytes an, statisch eines mit 900 Bytes und 
wickle die Arbeit an dem 500 Byte grossen Array über den 800-er 
Speicherblock ab. Der ist zwar größer als benöntigt, aber das stört ja 
nicht weiter.

: Bearbeitet durch User
von B. S. (bestucki)


Lesenswert?

Rufus Τ. F. schrieb:
> be s. schrieb:
>> das erste Mal benötigst du malloc
>
> Nö, realloc kann auch für das erste Mal verwendet werden. Ist der
> übergebene Pointer ein NULL-Pointer, verhält sich realloc wie
> malloc.
> ptr = realloc(NULL, newsize);

Danke, das wusste ich noch nicht.

Karl H. schrieb:
> Habe ich den Speicher
> und ich weiss dass Array1 500 Bytes benötigt, Array 2 braucht 900 Bytes
> und Array 3 benötigt 800 Bytes UND weiss ich zusätzlich, dass Array1 und
> Array 3 niemals zur selben Zeit benötigt werden, dann lege ich mir
> statisch ein Array mit 800 Bytes an, statisch eines mit 900 Bytes und
> wickle die Arbeit an dem 500 Byte grossen Array über den 800-er
> Speicherblock ab.

Wenn unterschiedliche Datentypen für die Arrays verwendet werden, legt 
man beide Arrays in einer union an, eigentlich auch schon nur, wenn man 
verschiedene Namen für die Arrays haben möchte. Eine der seltenen 
sinnvollen und sicheren Verwendungen einer union.

von Karl H. (kbuchegg)


Lesenswert?

be s. schrieb:

> Wenn unterschiedliche Datentypen für die Arrays verwendet werden, legt
> man beide Arrays in einer union an, eigentlich auch schon nur, wenn man
> verschiedene Namen für die Arrays haben möchte. Eine der seltenen
> sinnvollen und sicheren Verwendungen einer union.

Gute Idee. Daran hatte ich gar nicht gedacht.

von B. S. (bestucki)


Lesenswert?

Karl H. schrieb:
> be s. schrieb:
>
>> Wenn unterschiedliche Datentypen für die Arrays verwendet werden, legt
>> man beide Arrays in einer union an, eigentlich auch schon nur, wenn man
>> verschiedene Namen für die Arrays haben möchte. Eine der seltenen
>> sinnvollen und sicheren Verwendungen einer union.
>
> Gute Idee. Daran hatte ich gar nicht gedacht.

Nach etwas Nachdenken zu folgendem Schluss gekommen: Mit dieser Variante 
muss man sich einfach sicher sein, dass die Werte beim Verlassen der 
Funktion, die das Array verwendet, verworfen werden dürfen, bzw. man 
muss annehmen, dass die Werte verändert werden. Ansonsten könnte man 
sich eine kleine Hilfsfunktion basteln:
1
static unsigned char Array100[100];
2
static bool Array100Used = false;
3
4
void * malloc100(){
5
  if(Array100Used){
6
    return NULL;
7
  }
8
  Array100Used = true;
9
  return Array100;
10
}
11
12
bool free100(void * Ptr){
13
  if(Ptr == Array100){
14
    Array100Used = false;
15
    return true;
16
  }
17
  return false;
18
}

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Falls sich die Verwendung des zu nutzenden Speicherbereichs analog zu 
einer lokalen Variablen organisieren lästt, dann geht alloca bzw. Arrays 
variabler länge.  Diese Arrays gibt's ab C99 (aber in C11 u.U. nicht 
mehr).

Vorteil ist die einfache Verwendung und dass sie wesentlich weniger 
Verwaltungsoverhead benötigen: Diese Arrays werden auf dem Stack 
angelegt.

http://linux.die.net/man/3/alloca
1
#include <stdlib.h>
2
#include <stdint.h>
3
4
extern void use_array (size_t, uint8_t[]);
5
6
void func (size_t n_bytes)
7
{
8
    uint8_t array[n_bytes];
9
    use_array (n_bytes, array);    
10
}
11
12
#include <alloca.h>
13
14
void func_alloca (size_t n_bytes)
15
{
16
    use_array (n_bytes, alloca (n_bytes));
17
}

Der erzeugte Code für beide Funktionen ist gleich; es wird i.W. Platz 
auf dem Stack reserviert und am Ende wieder freigegeben.

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.