Wie funktioniert eigentlich realloc()?
Sucht das Betriebssystem(?) quasi bei einer Anfrage nach (zB.)
Vergrößerung des Speichers einen neuen zusammenhängenden
Speicherbereich? D.h, ich bekäme nach jedem:
addr = realloc(str, strlen(buffer)+new_len+1);
eine neue Adresse?
Und falls ja, was passiert dann mit dem alten Speicher? Dieser müsste ja
dann automatisch freigegeben werden, oder?
Falls realloc != 0/NULL returniert, dann kann es ein Pointer zu einem
anderen Speicherbereich sein (in dem Fall wird der alte Pointer mit free
freigegeben), oder aber der selbe, kommt drauf an wies gemacht ist.
Reallokierer schrieb:> Wie funktioniert eigentlich realloc()?> Sucht das Betriebssystem(?) quasi bei einer Anfrage nach (zB.)> Vergrößerung des Speichers einen neuen zusammenhängenden> Speicherbereich? D.h, ich bekäme nach jedem:> addr = realloc(str, strlen(buffer)+new_len+1);> eine neue Adresse?
Ja, darauf das sich addr bei jedem realloc() ändert muss Deine Software
vorbereitet sein. Aber immer passiert das nicht.
Reallokierer schrieb:> Und falls ja, was passiert dann mit dem alten Speicher? Dieser müsste ja> dann automatisch freigegeben werden, oder?
Der wird von der Speicherverwaltung recycelt und steht dann wieder als
freier Speicher für alloc() zur Verfügung.
Rufus Τ. Firefly schrieb:> Nicht das Betriebssystem, sondern die Heapverwaltung der> Laufzeitumgebung Deines Compilers.
... die dann für eine kräftige Fragmentierung sorgen können.
holger schrieb:> ... die dann für eine kräftige Fragmentierung sorgen können.
Nach der gar keiner gefragt hatte. Ich kann diese ewige „malloc ist
per se böse“-Leier einfach nicht mehr erhören.
Zum TE: der Variantenreichtum der Implementierungen ist dabei extrem
groß. Das ist auch eine Frage, ob du auf einem System mit virtuellem
Speicher arbeitest (bei dem man recht großzügig sein kann) oder es
eine eher kleine Umgebung mit chronischer Speicherknappheit ist.
holger schrieb:> ... die dann für eine kräftige Fragmentierung sorgen können.
Dafür braucht es kein realloc, das geht mit malloc, calloc und
free genausogut.
Autor: Rufus Τ. Firefly (rufus) (Moderator) schrieb:
>Nicht das Betriebssystem, sondern die Heapverwaltung der>Laufzeitumgebung Deines Compilers.
Dasch a ja interessant. Also kwasi so ne Art "Middleware" zwischen OS
und Anwendung.
73
Rufus Τ. Firefly schrieb:> ... das geht mit malloc, calloc und free genausogut.
auf diesem Weg macht es realloc
@Jörg Wunsch
deswegen:
> ... die dann für eine kräftige Fragmentierung sorgen können.
achte auf das "sorgen können". Eine
> „malloc ist per se böse“-Leier
kann Ich in der Zeile nicht erkennen. Und ja, der TE hatte nicht danach
gefragt. Etwas Hintergrund kann aber nicht schaden.
holger schrieb:> Etwas Hintergrund kann aber nicht schaden.
Dann präzisiere das doch. Denn auf einem halbwegs modernen PC wirst du
schon ziemlich viel tun müssen bis du da etwas merkst.
holger schrieb:> auf diesem Weg macht es realloc
Aha, du weißt es ja genau.
Hast du denn selbst schon mal eins geschrieben oder dir angesehen,
wie eins funktioniert, welches andere geschrieben haben?
Man kann es sicher auf diesem Wege machen, aber das ist eher ein
Notnagel denn eine vernünftige Implementierung.
Reallokierer schrieb:> Also quasi so ne Art "Middleware" zwischen OS und Anwendung.
Ja, nennt sich Standardbibliothek. OS muss übrigens nicht sein.
Autor: Jörg Wunsch (dl8dtl) (Moderator)
> OS muss übrigens nicht sein.
Stimmt, jetzt wo du's wieder sagst... Für den Arduino Due mit SAM3X8E
mit 64 + 32 Kbytes RAM steht der Befehl zur verfügung.
Was meinen die Experten? den Befehl verwenden? Oder eher Finger
weglassen?
73
Reallokierer schrieb:> Autor: Jörg Wunsch (dl8dtl) (Moderator)>> OS muss übrigens nicht sein.> Stimmt, jetzt wo du's wieder sagst... Für den Arduino Due mit SAM3X8E> mit 64 + 32 Kbytes RAM steht der Befehl zur verfügung.>> Was meinen die Experten? den Befehl verwenden? Oder eher Finger> weglassen?
Das kann man pauschal nicht sagen. Da Du aber auf eine Prozessor mit
64kB + 32kB RAM verweist (der möglicherweise nicht insgesamt von malloc,
realloc verwaltetet wird), kann man hinzufügen, da0 die allgmeine
Tendenz eher dahin geht, malloc/realloc bei sowenig (soviel) RAM zu
vermeiden.
Es kommt auch auf den Verwendungszweck an. Wenn ein Programm relativ
viele kleine Blöcke alloziiert ist das auf einem PC mit Gigabytes eher
akzeptabel, als auf einem uC mit einigen Kilobyte. Du musst bedenken,
dass die Speicherverwaltung selbst Speicher benötigt. Einige (soweit mir
bekannt die allermeisten) Speicher-Verwaltungen benötigen einige Bytes
pro angefordertem Block (genauer: zusammenhängendem Speicherbereich).
Bei wenig Speicher und Anforderungen von Bereichen die nicht wesentlich
über dem Overhead liegen, wird das schnell ineffizient.
kommt drauf an...
Man muß sich halt bewusst sein, welche Resourcen man hat, wieviel man
braucht und was man sinnvollerweise macht, wenn es eng wird.
Wenn man den Überblick nicht hat (warum auch immer), dann besser die
Finger davon lassen.
Das ist wie mit gutem (scharfem) Werkzeug. Wenn man nicht damit umgehen
dann, dann lieber nicht anfassen.
Reallokierer schrieb:> den Befehl verwenden?
Wenn du ein dynamisches Problem hast, wirst du mit einer statisch
vorallozierten Implementierung kaum besser kommen als mit malloc(),
denn du riskierst, dass deine Implementierung Bugs mit sich bringt,
die aus dem „offiziellen“ malloc() alle schon rausgeschüttelt sind.
Du solltest dir natürlich genau ansehen, auf welche Weise man das
mit der Newlib daherkommende (und durch deine selbst zu erstellende
_sbrk()-Backend-Funktion zu untersetzende) malloc() „auf die sichere
Seite“ bekommt, sprich, das Risiko einer Kollision von Heap und Stack
möglichst gering zu bekommen.
Eine Anwendung mit dynamischem Datenanfall muss immer für die
Situation gerüstet sein, dass sie mehr Daten bekommen könnte, als
sie Speicher zur Verfügung hat, und muss daraus einen Ausweg haben.
Das ist komplett unabhängig davon, ob man die Verwaltung einem
malloc() überlässt oder statisch vorbelegt (bei letzterem wird der
Fall sogar mit großer Wahrscheinlichkeit schon eher eintreten).
Wenn du ein rein statisch lösbares Problem hast, stellt sich die Frage
eigentlich gar nicht. ;-)
Man könnte noch folgende "Daumenregeln" hinzufügen:
1. Auf einem uC ist der Speicherbedarf bei den allermeisten Anwendungen
von vorneherein bekannt oder es kann ein Maximalbedarf benannt werden.
Dann würde man eher ein Array definieren.
2. Falls dieses Array nicht per se wie ein Ringpuffer oder
Kellerspeicher bedient wird, schreibt man sich eine eigene kleine
Verwaltung für die Einträge (ein paar Zeiger oder/und ein Flag in einer
Struktur die dann in dem Array gespeichert wird).
3. Falls man aber viele solcher Arrays mit kleinen Bereichen für die
Speicherung der Anwendungsdaten braucht dann gibt es einen Grenzwert in
dem die eigene Verwaltung genauso viel Overhead erzeugt wie die von
malloc/realloc. Dann kann man 2. ebensogut sein lassen.
4. Auf einem uC allerdings wird man einigen Aufwand in Vorüberlegungen
stecken um malloc/realloc zu vermeiden.
5. Auf einem PC hingegen ist der Speicherbedarf oft nicht bekannt.
Insbesondere die Abfolge der nötigen Anforderungen ist oft nicht bekannt
(im gegensätzlichen Fall könnte man einige logisch getrennte Variablen
zusammenlegen, da sie immer zeitlich und kausal zusammenhängend
gespeichert werden müssen).
6. Auf einem PC ist Speicher meist im Überschuss vorhanden, so dass die
Verwendung von malloc/realloc meist garnicht in Frage steht.
7. Allerdings gibt es auch Anwendungen für den PC die den Speicher voll
ausnutzen (z.B. Bild- oder Videobearbeitung) und in denen Swapping aus
Zeitgründen vermieden werden soll. Dann greifen die unter 3. bzw. 5.
genannten Maßnahmen.
Bitflüsterer schrieb:> 2. Falls dieses Array nicht per se wie ein Ringpuffer oder> Kellerspeicher bedient wird, schreibt man sich eine eigene kleine> Verwaltung für die Einträge (ein paar Zeiger oder/und ein Flag in einer> Struktur die dann in dem Array gespeichert wird).
Dann bist du bei dem Punkt, den ich oben nannte: das Risiko, da Bugs
einzubringen, ist deutlich größer, als wenn man gleich malloc() nimmt.
Letztlich ist malloc() auf einem Controller auch weiter nichts als
die Verwaltung eines großen Byte-Arrays, welches sich zwischen dem
Ende der statischen Daten und dem Stack befindet. Beim malloc() aus
der (hier relevanten) Newlib kann man es sogar vollständig so nutzen,
da man dort die Backend-Funktion _sbrk() ohnehin selbst liefern muss,
die den nächsten freien Speicherblock anfordert.
Aber auch beim statischen Array als „Heap“ hat man das Risiko, wenn
man dieses zu groß macht, dass da der Stack hineinrennt … Nur, dass
da eben kein malloc() mehr eine 0 zurückgeben kann.
Ich möchte sowohl Overhead minimieren, als auch die Fragmentierung des
Speichers verhindern.
Ich habe mir vorgestellt wie ich selbst "realloc" implementieren würde:
falls neue Länge kleiner als jetzige ist, dann nur das Delta am Ende
freigeben und Fertig!
Nun meine Fragen: Wenn es garantiert ist, dass in der Zukunft die
Größe/Länge des allokierten Speichers gleich bleibt oder kleiner wird,
1. bleibt die Anfangs-Adresse gleich? (ISO, C99 od. Sonstiges?)
2. werden die nicht mehr benötigten Bytes ("Delta") am Ende freigegeben?
1
/*****************************************
2
Am Anfang (nach "setup()" Aufruf.)
3
Addr. ---------------------
4
0x0815 |0|1|2|3|4|5|6|7|8|9|
5
---------------------
6
7
Nach aufruf von "realloc(5, sizeof(uint8_t))" ?
8
Addr. ---------------------
9
0x???? |0|1|2|3|4|freigeg.?|
10
---------------------
11
******************************************/
12
13
#define MAX_SIZE 10
14
15
uint8_t*ptr;
16
17
voidsetup()
18
{
19
*ptr=(uint8_t*)calloc(MAX_SIZE,sizeof(uint8_t));
20
}
21
22
voidloop()
23
{
24
// Tue dies..., tue das...
25
26
newLength=5;// Neue Länge ist garantiert kleiner als MAX_SIZE.
Otto Realverbraucher schrieb:> Wenn es garantiert ist, dass in der Zukunft die Größe/Länge des> allokierten Speichers gleich bleibt oder kleiner wird,> 1. bleibt die Anfangs-Adresse gleich? (ISO, C99 od. Sonstiges?)> 2. werden die nicht mehr benötigten Bytes ("Delta") am Ende freigegeben?
Nichts davon ist garantiert.
Was die avr-libc-Implementierung betrifft, handhabt sie das im Prinzip
so, aber den Sourcecode kannst du dir ja selbst ansehen.
Jörg W. schrieb:> Nichts davon ist garantiert.>> Was die avr-libc-Implementierung betrifft, handhabt sie das im Prinzip> so, aber den Sourcecode kannst du dir ja selbst ansehen.
Vielen Dank Jörg W.!
Um zu garantieren, dass die anderen globale Variablen im Heap, alle nach
dem allokierten Bereich platziert werden, bringt es was den allokierten
Array als Erstes zu definieren und Speicherplatz zuweisen (statisch)?
z.B.:
uinit8_t * ptr = (uint8_t*)malloc(MAX_SIZE * sizeof(uint8_t));
bool b = false;
char c = 'c';
int16_t i = 0;
...
usw.
Ich denke nicht, dass solche Verrenkungen irgendwelcher Art wirklich
was bringen.
Entweder hast du ein Problem, welches sich mit statisch belegtem
Speicher erledigen lässt, d. h. alle Details sind zur Compilezeit
bekannt. Dann brauchst du kein malloc(). Oder du hast wirklich
dynamische Daten, weißt zur Compilezeit also nicht, was wirklich wann
auf dich zukommt. Dann brauchst du 1.) immer (egal ob malloc()
oder nicht) eine „Exit-Strategie“, also irgendeine Maßnahme, die du
dann ergreifen kannst, wenn die Datenflut größer wird, als deine
Speichermöglichkeiten es hergeben. 2.) brauchst du dann einen
Allokator, bspw. eben malloc().
Bei wirklich zufälligen Anforderungen an die Größe der jeweiligen
Speicherblöcke ist die Gefahr von Fragmentierung nicht so groß, wie
das gemeinhin angenommen wird. Sofern überhaupt noch ein paar
Reserven da sind, gleicht sich das über die Programmlaufzeit wieder
aus.
Deine Variablen „ptr“, „b“, „c“ und „i“ spielen dabei nur insofern
eine Rolle, als dass sie beim Eintritt in die Funktion Stack belegen
könnten (oft genug wird sie der Compiler ohnehin in Registern
halten). Der für den Stack benutzte Platz steht dem Heap nicht mehr
zur Verfügung, der ist also schon weg, bevor dein malloc() da
überhaupt aufgerufen wird.