Mahlzeit,
oft wird einem ja von malloc() auf einem uC abgeraten.
Manchmal ist es aber doch ganz praktisch: z.B. ich habe irgendwelche
Datensätze, die in Strukturen organisiert sind. Meinetwegen Adressen.
Weil Straßennamen ja doch eine recht unterschiedliche Länge haben können
macht es ja Sinn, anstatt eines Members „char adresse[MAX_LEN]“ einen
Member „char *adresse“ anzulegen und den String separat anzulegen.
Eine Ähnliche Situation hab ich momentan, deswegen beschäftige ich mich
mit malloc(), was ich bisher eher selten genutzt habe.
Aber wie funktioniert denn malloc() genau auf einem AVR?
Dort ist ja erstmal kein OS vorhanden, dass meinen Speicher verwaltet.
Woher nimmt malloc() den Speicher, und woher weiß es wieviel noch frei
ist? Wie gibt es Speicher wieder frei?
Würde doch gern bischen über die Internas Bescheid wissen, sonst ist mir
das Ganze nicht geheuer :-)
Gruß, Paul
Dazu braucht man auch kein OS, der Speichermanager, dh eine
Libraryfunktion, uebernimmt die Verwaltung des unbenutzten Speichers.
Ich wuerd's auch ohne dynamischen Speichermachen.
Paul schrieb:> Aber wie funktioniert denn malloc() genau auf einem AVR?> Dort ist ja erstmal kein OS vorhanden, dass meinen Speicher verwaltet.
Und wie macht das OS das?
Mit deiner Argumentation verschiebst du das Problem ja nur. Nur weil du
ein OS hast, löst sich ja der Problemkreis deswegen nicht in Luft auf
:-)
> Woher nimmt malloc() den Speicher, und woher weiß es wieviel noch frei> ist? Wie gibt es Speicher wieder frei?
Na zb, indem es den überhaupt freien Speicher erst mal als
Speicherblöcke betrachtet über die Buch geführt wird.
Machst du eine Allokierung, dann wird ein möglichst gut passender Block
gesucht. Passt der genau, dann kriegst du einen Pointer darauf und der
Block kommt aus der 'Buchhaltung' raus. Passt der Block nicht genau,
dann wird er in 2 Teile aufgeteilt, der eine, der genau passt, den
kriegst du, der andere bleibt in der Buchhaltung.
Gibst du Speicher wieder frei, passiert genau das umgekehrte. Der Block
geht wieder zurück in die Buchhaltung und es wird versucht, ob man
eventuell 2 benachbarte kleinere Blöcke zu einem größeren verschmelzen
kann.
Organisiert ist das ganze normalerweise als lineare Liste. D.h. in jedem
Block steht die Adresse des jeweils nächsten Blocks und oft hält man
diese Liste auch nach Adressen sortiert, weil man dann die Blockgröße
durch Adressvergleich ermitteln kann und weil man beim free(), beim
Verschmelzen, sowieso die benachbarten Blöcke suchen muss.
Paul schrieb:> Manchmal ist es aber doch ganz praktisch
Von malloc wird nicht abgeraten, weil es unpraktisch waere. Mach dir mal
Gedanken ueber das Thema "Speicherfragmentierung" und was es fuer
Systeme mit wenig Speicher bedeutet.
> Eine Ähnliche Situation hab ich momentan
Tja. Das Problem ist, das du zwar rein rechnerisch noch 10k frei haben
kannst und trotzdem keinen Speicher mit 64Bytes allokieren kannst, weil
du nur lauter kleine Speicherstückchen frei hast, die zwar in Summe die
10k ergeben, aber von denen keiner größer als sagen wir mal 32Bytes ist.
Je mehr Speicher du insgesamt hast, auch im Vergleich zu deinen
Allokierungen, desto geringer wird diese Gefahr. Je weniger Speicher
überhaupt verfügbar ist, desto größer wird die Gefahr.
Und da hat man auf einem µC lieber gesicherte Zahlen. Sag ich meinem
Benutzer, du kannst 100 Adressen ins Adressbuch legen, wobei der
Strassenname maximal 40 Zeichen umfassen darf, dann hat er was, mit dem
er arbeiten kann. Ist aber nach 80 Adressen schon Schluss, weil ein
Strassenname mit 120 Zeichen nicht mehr angelegt werden kann, dann wird
er mir was husten.
Fragmentierung ist klar, die gibt es natürlich.
Das „Buchhaltungsprinzip“ kenn ich auch. Allerdings sind da doch
bestimmt einige Einträge nötig pro angeforderten Datenblock. Sprich, ich
habe einiges an Overhead, vor allem bei vielen kleinen Blöcken, richtig?
Und malloc() weiß ja zu Beginn auch noch nicht, wieviele Blöcke es
später mal zu verwalten hat, sprich, malloc() müsste evtl. auch
dynmischen Speicher anfordern?
Paul schrieb:> Fragmentierung ist klar, die gibt es natürlich.>> Das „Buchhaltungsprinzip“ kenn ich auch. Allerdings sind da doch> bestimmt einige Einträge nötig pro angeforderten Datenblock.
Nicht wirklich.
Wenn du den Adresspointer kriegst, dann ist er raus aus der
Speicherverwaltung. Der belegt in der Verwaltung nichts mehr, der
Speicher gehört dir.
> Sprich, ich> habe einiges an Overhead,
Innerhalb der Verwaltung hast du einen Pointer in jedem Block und eine
globale Variable, die auf den ersten Block zeigt.
> Und malloc() weiß ja zu Beginn auch noch nicht, wieviele Blöcke es> später mal zu verwalten hat, sprich, malloc() müsste evtl. auch> dynmischen Speicher anfordern?
No. die Information darüber liegt sinnvollerweise in den Speicherblöcken
selber. Ist ja nur ein Pointer auf den jeweils nächsten Block.
Und auf einem µC kommt noch was dazu.
Du weißt am Anfang nicht, wieviel Speicher du für den Heap überhaupt
hast. Heap und Stack müssen sich ja denselben Speicher teilen.
Der Stack wächst traditionell von oben nach unten. Wieviel Stack
gebraucht wird hängt davon ab, wieviele und welche funktionslokale
Variablen du hast, wie sich Funktionen gegenseitig aufrufen, was
wiederrum des öfteren von Aussen gesteuert wir (Benutzereingaben, etc.).
Die Heap-Allokierungen finden von unten nach oben statt. Und irgendwann
treffen sich die beiden, wenn man Pech hat.
Paul schrieb:> Aber wie funktioniert denn malloc() genau auf einem AVR?
RTFDoc:
http://www.nongnu.org/avr-libc/user-manual/malloc.htmlKarl Heinz Buchegger schrieb:> Tja. Das Problem ist, das du zwar rein rechnerisch noch 10k frei haben> kannst und trotzdem keinen Speicher mit 64Bytes allokieren kannst, weil> du nur lauter kleine Speicherstückchen frei hast, die zwar in Summe die> 10k ergeben, aber von denen keiner größer als sagen wir mal 32Bytes ist.
Ja, das kann passieren.
Allerdings ist es bei einem wirklich dynamischen Problem so, dass
man bei statischer Vergabe (mit Annahme entsprechender Maixmalwerte)
dann in der Regel viel weniger unterbringt, weil man den Speicher
deutlich schlechter ausnutzt, oder man muss mehr in einen größeren
Speicher investieren. Du garantierst deinem Nutzer die 100 Adressen
mit maximal 40 Zeichen langen Straßennamen, aber obwohl der Speicher
dann am Ende nur zu 1/3 ausgenutzt ist, bekommt er partout keine
101. Adresse mehr unter. Paul würde ihm bei gleichem Speicherausbau
"durchschnittlich 200 Adressen" anbieten können, sehr wahrscheinlich
bekommt der Nutzer sogar noch mehr unter, und auch ein einzelner
Straßenname mit 45 Zeichen ist kein Problem.
In jedem(*) Falle muss der Nutzer bzw. die Applikation jedoch mit der
Situation umgehen können, dass der Speicher einfach irgendwann voll
ist. Dessen muss man sich von vornherein bewusst sein.
(*) Also keinesfalls nur in der malloc-Variante!
Das Problem der Speicherfragmentierung wird um so größer, je stärker
der Speicher dann an der Auslastungsgrenze arbeitet. Wenn man ein
wirklich dynamisches Problem (mit sich häufig ändernden, variabel
langen Datenfeldern) hat und es ist noch "etwas Luft", dann korrigert
sich das statisch gesehen über die Lebensdauer einfach von selbst,
weil ja auch immer wieder was freigegeben wird. Je größer die
Auslastung des Speichers wird, um so schwieriger wird es für den
Allokator dann, einen hinreichend großen passenden freien Block noch
zu finden, bis dann irgendwann "Pumpe" ist.
Karl Heinz Buchegger schrieb:> Du weißt am Anfang nicht, wieviel Speicher du für den Heap überhaupt> hast. Heap und Stack müssen sich ja denselben Speicher teilen.
Nun, das Problem hast du allerdings bei statischer Allozierung
genauso: du weißt am Anfang auch nicht, wieviel Stack du wirklich
brauchst und ob dein allozierter Datenbereich vielleicht wirklich
irgendwann mit dem Stack kollidieren wird. Nun bist du entweder
übervorsichtig und planst für den Stack genügend Reserve ein (dann
sinkt wiederum die relative Speicherauslastung noch mehr), oder du
riskierst einen Komplettcrash wegen einer Kollision des Datenbereichs
mit dem Stack. Mit malloc() (zumindest dem der avr-libc) ist diese
Situation sogar noch geringfügig besser in diesem einen Fall, da
vor dem Allozieren eines weiteren Bereichs in Richtung Stack
zumindest getestet wird, ob der Bereich (zuzüglich einer einstellbaren
Sicherheitsreserve) bereits mit dem aktuellen Stack kollidiert oder
nicht.
Alles in allem sind derartige Probleme in einer so stark eingeschränkten
Umgebung wie einem kleinen Controller immer mit irgendwelchen Problemen
behaftet. Mit genügend Geld (=> mehr Speicher) kann man dabei die
Grenze immer in Richtung "sicherer" verschieben.