Hallo,
ich verstehe da eine "Kleinigkeit" nicht.
Erstmal die Sache die ich verstehe:
Dynamischen Speicher allokiere ich mit malloc() usw. und gebe ihn mit
free() wieder frei. Die Daten die dabei entstehen liegen im Heap, wobei
dieser Heap in der Regel im vorderen Bereich des RAMs liegt (hinter bss,
data usw). So weit so gut.
Jetzt lese ich oft, dass dynamischer Speicher nicht das Wahre für MCUs
ist.
Man sollte lieber seinen RAM statisch verwalten.
Wie geht das (einfaches Codebeispiel)?
Und wo liegt dieser Speicher dann? Im Stack? Zwischen Heap und Stack?
Ist eine lokale Variable somit statische Speicherverwaltung, weil sie
beim Funktionsaufruf in den Stack eingelesen wird und am Ende der
Funktion dann verworfen wird?
Oder ist statische Speicherverwaltung wenn ich gezielt auf Adressen im
RAM schreibe und diese dann mit einem Zeiger verbinde?
Darf man das, bzw sollte man das? Was ist wenn diese Adresse schon einen
Wert beinhaltet den eine andere Variable benutzt?
Oder ist statische Speicherverwaltung das Anlegen von globalen
Variablen?
Da Frage ich mich jedoch, wie man da mit dem Speicher auskommt, wenn man
mehrere Datenmengen hat die komplett nicht in den RAM passen, jedoch
wenn man sie einzeln initialisieren würde.
Irgendwie verstehe ich das nicht so recht.
Vielen Dank für eure Antworten.
p.s. wenn ihr ein gutes Tutorial dazu kennt, dann her damit.
char0n schrieb:> Und wo liegt dieser Speicher dann? Im Stack? Zwischen Heap und Stack?
bss oder data, je nach dem.
char0n schrieb:> Ist eine lokale Variable somit statische Speicherverwaltung,
Nein, eine lokale Variable ist nur eine andere Art von dynamischen
Speicher.
char0n schrieb:> Oder ist statische Speicherverwaltung das Anlegen von globalen> Variablen?
Ja.
char0n schrieb:> Da Frage ich mich jedoch, wie man da mit dem Speicher auskommt, wenn man> mehrere Datenmengen hat die komplett nicht in den RAM passen, jedoch> wenn man sie einzeln initialisieren würde.
Wenn die Daten gleichzeitig benutzt werden, passen sie so oder so nicht
rein. Und wenn man sie nicht gleichzeitig benutzt, kann man ja auch
statischen Speicher "recyceln". Wenn sich die Daten in ihrer Art
unterscheiden z.B. am besten per union.
Statische Speicherverwaltung heißt, dass du zur Compilezeit weist wie
viel RAM benötigt wird. Erreicht wird das wie du schon vermutet hast
durch globale Variablen. Diese liegen dann in der data oder bss Sektion.
Wenn du die Variablen im Stack anlegst ist das auch dynamisch da du
normalerweise nicht genau weist auf welche verschachtelten Wege deine
Funktion aufgerufen wird, deshalb sollte man in Funktionen keine großen
Variablen auf den Stack legen. An ein paar ints scheitert es meistens
nicht, dafür ist der Stack ja auch gedacht.
Das Problem mit den mehreren Datenmengen die nicht gleichzeitig ins RAM
passen muss man entweder dynamisch lösen wobei man sich genau Gedanken
darüber machen muss ob das klappt oder man legt z.B. ein Byte array an
1
uint8_t daten[256];
und benutzt das dann einmal für die 1. Daten und danach dann für die 2.
Daten. Wenns um Strukturen geht kann man das meist noch elegant mit
Unions lösen um nicht soviel casten zu müssen. Beide Varianten gehen
natürlich nur, wenn sichergestellt ist, dass die Daten nicht
gleichzeitig benutzt werden.
Vielen vielen Dank für eure Antworten!
Das hat mir schonmal gut geholfen, jedoch auch weitere Fragen
aufgeworfen. Würdet ihr euch vielleicht noch diese Frage zu Gemüte
führen? :-)
Stefan Ernst schrieb:> char0n schrieb:>> Und wo liegt dieser Speicher dann? Im Stack? Zwischen Heap und Stack?>> bss oder data, je nach dem.>
Woran liegt das denn?
Ich arbeite zur Zeit mit einem STM32 und bei dem kann ich in der Startup
Assembler Datei die Größe vom Heap und Stack definieren (siehe unten).
Wieso kann ich da keine bss oder data definieren. Und vorallem wundere
ich mich das der Stack und Heap so klein definiert ist, immerhin habe
ich über 100kByte internen RAM, laut dem Code habe ich "nur" 1kByte
Stack und 512Byte Heap... Wo ist der Rest hin? Ich dachte eigentlich
Heap und Stack würden sich berühren und davor liegen bss und data, wie
in dem Bild im Anhang. Somit sind laut dem Assemblercode mehrere kByte
ungenutzt?! Verstehe ich nicht... :-/
Was mich auch interessiert, wenn ich eine globale Variable anlege und
diese dann in einer Funktion beschreibe oder dergleichen, wird diese
dann auch in den Stack geschrieben? Das würde für mich dann ja heißen,
dass der Stack mindestens so groß sein muss wie die Summe aller
Variablen die in einer Funktion aufgerufen werden könnten. :-/ Gibts
keine maximale Größte für den Stack?
André Althaus schrieb:> und benutzt das dann einmal für die 1. Daten und danach dann für die 2.> Daten. Wenns um Strukturen geht kann man das meist noch elegant mit> Unions lösen um nicht soviel casten zu müssen. Beide Varianten gehen> natürlich nur, wenn sichergestellt ist, dass die Daten nicht> gleichzeitig benutzt werden.
Gleichtzeitig werde ich sie nicht nutzen, aber nacheinander... das mit
dem Unions muss ich mir mal ansehen.
Kann ich denn in einer Union nicht nur verschiedene Datentypen, sondern
auch verschiedene Größen für die Arrays nutzen und gleichzeitig die
gleiche Startadresse haben?
Bsp.:
> Was mich auch interessiert, wenn ich eine globale Variable anlege und> diese dann in einer Funktion beschreibe oder dergleichen, wird diese> dann auch in den Stack geschrieben?
Der Stack dienst als Zwischenspeicher für Funktionen. Globale Variablen
liegen an einer dem Compiler bekannten festen Stelle im RAM und die
Werte werden dort geschrieben (deshalb global).
Beim Booten legt man den Anfang des Stacks in Form des Stackpointers an
eine Stelle des RAMs (oft ans Ende) und bei jedem Funktionsaufruf wird
der Pointer in der Funktion verschoben. Man weis also nicht wie viel
Platz der Stack braucht, da dies von dem genauen Verlauf der
Funktionsaufrufe abhängt. Deshalb steht im Assemblercode auch
> ; Tailor this value to your application needs
Wenn du die Stackgröße zu klein wählst und der Heap direkt an den Stack
grenzt kann es passieren dass bei einem Funktionsaufruf der Stack in den
Heap hineinwächst und dort Werte überschreibt. Du legst hier auch keine
endgültige Stackgröße (der Stack kann auch größer werden) an, sondern
nimmst soviel dass du sicher bist, dass diese Größe nicht überschritten
wird. Überprüfen und messen kann man den Stackverbrauch indem man den
Stack am Anfang mit einem Muster komplett füllt (z.B. 0xAA) und in
regelmäßigen Abständen (überprüfen ob der Stack nicht zu groß wird) bzw.
nachdem das Programm lange gelaufen ist und somit alle möglichen
Funktionsverläufe aufgetreten sind (Stackverbrauch ermitteln) einfach
nachschaut wie weit das Muster noch vorhanden ist.
Der Heap ist der Speicher den malloc/free zu Verfügung haben, den legst
du hier auch selber fest je nachdem wie viel deine Anwendung braucht.
bss (genullte globale Variablen) und data (mit Werten initialisierte
globale Variable) brauchst du nicht festlegen, das macht der Compiler
für dich. Du sagst ihm im Code was für globale Variablen du brauchst und
der Compiler macht die beiden Sektionen dann so groß wie nötig.
An welcher Stelle im Flash und RAM die einzelnen Sektionen landen legt
das Linkerskript fest. Bei deiner Architektur dürfte es dann so aussehen
wie auf dem Bild wobei ich vermute, dass zwischen Heap und Stack der
ganze ungenutze RAM liegt.
> Kann ich denn in einer Union nicht nur verschiedene Datentypen, sondern> auch verschiedene Größen für die Arrays nutzen und gleichzeitig die> gleiche Startadresse haben?
Wenn du folgendes machst
1
union test {
2
uint8_t daten[256];
3
uint8_t daten2[512];
4
uint32_t daten3[1024];
5
}
Dann ist der Union insgesamt so groß wie die größte Komponente also
1024*sizeof(uint32_t). Alle drei Arrays fangen an der gleichen
Speicheradresse an. Wegen den gleichen Datentypen wäre test.daten eine
Teilmenge von test.daten2.
char0n schrieb:> Jetzt lese ich oft, dass dynamischer Speicher nicht das Wahre für MCUs> ist.> Man sollte lieber seinen RAM statisch verwalten.
Ja, genau.
Beispiel aus dem realen Leben (Amiga A2000, der hatte das Problem auch):
Du hast 8 MB zusammenhängendes RAM. Jetzt forderst Du nacheinander
2,5MB, 1MB und 4MB an Speicher an. Bleibt also noch 0,5MB frei. Jetzt
gibst Du den 2,5MB-Block und den 4MB-Block wieder frei. Du hast jetzt
wieder 7MB freien Platz. Jetzt forderst Du 6MB an und bekommst eine
Fehlermeldung. Wieso? Ganz klar, Du hast zwar in der Summe 7 MB frei,
das aber nicht an einem Stück, weil der 1MB-Block mitten im Adressraum
liegt. Der Adressraum fragmentiert im Laufe der Zeit, weil das
Betriebssystem die belegten Blöcke nicht umherschieben konnte, um Platz
zu schaffen. Da half dann nur ein Reboot.
Bei Systemen mit einer MMU sind logischer und physikalischer Adressraum
voneinander getrennt, d.h. jedes Programm hat seinen eigenen
Adressbereich, sodass eine Fragmentierung des Adressraums nicht so
leicht auftritt.
Windows und das alte MacOS haben es anders gemacht. Du hast bei einem
LocalAlloc nur ein Handle bekommen. Erst nach einem LocalLock hast Du
einen Pointer auf Deinen Speicher bekommen. Nach einem LocalUnlock
konnte das Betriebssystem den Speicherbereich umherschieben, um einer
Fragmentation entgegenzuwirken.
Das AmigaDos hatte keinen derartigen Mechanismus, wegwegen der Speicher
im Laufe der Zeit fragmentierte. ucLinux (die Spar-Version für
Prozessoren ohne MMU) hat prinzipbedingt das gleiche Problem.
Daher ist die empfehlenswerte Strategie im Embedded-Bereich die, ALLE
benötigten Resourcen EINMAL am Anfang anzufordern und anschließend damit
zu arbeiten.
fchk
char0n schrieb:> Was mich auch interessiert, wenn ich eine globale Variable anlege und> diese dann in einer Funktion beschreibe oder dergleichen, wird diese> dann auch in den Stack geschrieben? Das würde für mich dann ja heißen,> dass der Stack mindestens so groß sein muss wie die Summe aller> Variablen die in einer Funktion aufgerufen werden könnten.André Althaus schrieb:>> Der Stack dienst als Zwischenspeicher für Funktionen. Globale Variablen> liegen an einer dem Compiler bekannten festen Stelle im RAM und die> Werte werden dort geschrieben (deshalb global).
Danke für die ausführliche Erklärung, jedoch konnte ich daraus nicht
rauslesen, ob die globale Variable komplett in den Stack gelegt wird
oder nur die Adresse zu der globalen Variable, sobald diese Variable in
einer Funktion genutzt wird. Wenn der komplette Wert in den Stack
gelesen wird, dann muss ich ja den Stack mindestens so groß machen wie
theoretisch globale Variablen in der Funktion aufgerufen werden könnten.
Also wie ist das nun?
Oder muss ich dann darauf achten wie ich den Wert übergebe, Stichpunkt
"Call by Value/Reference"?
Vielen Dank nochmal.
weis der Compiler z.B. dass global an Adresse 0x10 liegt. Und dann wird
da pseudo Assembler mäßig nur ein
1
lade register0 in adresse 0x10
2
return
Wenn du eine globale Variable in einer Funktion benutzt landet nichts
davon im Stack. Du kannst dir ja auch mal den Assemblercode anschauen,
da sieht man was auf den Stack geschrieben wird. Ich finde durch
anschauen vom Assemblercode lernt man sowas am besten. Vielleicht auch
mal die elf Datei vollständig disassemblern dann siehst du auch wo die
einzelnen Sektionen landen.
> Oder muss ich dann darauf achten wie ich den Wert übergebe, Stichpunkt> "Call by Value/Reference
Die Werteübergabe der Parameter hat mit globalen Variablen nichts zu
tun. Bei allen Standard Datentypen (char, short, int) werden bei "Call
by Value" die Werte normalerweise direkt in Registern übergeben. Nur
wenn keine Register mehr frei sind (weil du z.B. ein array by Value
übergibts) werden die Werte auf den Stack geschrieben.
Das hängt natürlich alles von der Architektur und der Sprache ab. Ich
beziehe mich hier auf C.
Als Faustregel alle Werte die in einfache Datentypen passen per Value
übergeben, alles größere (Array, Struktur) per Zeiger (oder Referenz je
nach Sprache)
Falls noch was unklar ist poste mal ein kompilierbares Minimalbeispiel
an konkreten Code ist es etwas einfacher die Sachen zu erklären.
Ich kann mich hier nur am laufenden Band bedanken!
Das Beispiel hat mir sehr gut geholfen!
Ich hatte mir noch überlegt was in diesem Fall wäre:
1
intglobal;
2
3
voidtest(void)
4
{
5
intfoo;
6
7
foo=global;
8
}
Dabei würde die Variable foo dynamisch erstellt und im Stack landen. Bei
der statischen Speicherverwaltung will ich ja genau das verhindern.
Richtig?
Dieses Beispiel kommt mir auch noch in den Sinn:
1
intfoo;
2
intbar;
3
4
voidtest(void)
5
{
6
foo=bar;
7
}
Entsteht hier wieder ein Pseudocode der foo die Adresse von bar
übergibt?
Somit liegen foo und bar auf der selben Adresse und verweisen auf den
gleich Wert.
Richtig?
Wenn du meine Beispiele und Erklärungen dazu absegnest bin ich voll auf
zufrieden! :)
Danke!
char0n schrieb:> Dieses Beispiel kommt mir auch noch in den Sinn:> int foo;> int bar;>> void test(void)> {> foo = bar;> }>> Entsteht hier wieder ein Pseudocode der foo die Adresse von bar> übergibt?> Somit liegen foo und bar auf der selben Adresse und verweisen auf den> gleich Wert.> Richtig?
nein, foo und bar habe beide ihre eigene speicherzelle.
Hier wird der inhalt von der speicherzelle von bar in den Inhalt der
speicherzelle von foo geschrieben.
Danach steht also 2mal im speicher der gleiche wert.
Peter II schrieb:> Hier wird der inhalt von der speicherzelle von bar in den Inhalt der> speicherzelle von foo geschrieben.>> Danach steht also 2mal im speicher der gleiche wert.
Läuft das Schreiben von bar in foo dann über den Stack?
> Dabei würde die Variable foo dynamisch erstellt und im Stack landen. Bei> der statischen Speicherverwaltung will ich ja genau das verhindern.> Richtig?
Genau (wenn es nur sehr wenig Code ist auch direkt in einem Register),
aber eine Funktion braucht ja bestimme Variablen als Zwischenspeicher.
Solange es nur um ein paar einfache Datentypen geht, sollte man sich um
den Stack nicht soviel Sorgen machen, zumal du ja genug RAM hast, ob du
den Stack dann 1kB oder 2kB groß machst ist dann egal.
Es geht bei der statischen Speicherverwaltung besonders um so Sachen wie
Puffer, Warteschlangen, große Arrays und Strukturen, die halt mehr als
ein paar Byte belegen und für die man sonst dynamisch bei Bedarf den
Speicher anfordern würde.
> void test(void)> {> foo = bar;> }
Über den Stack sollte hierbei nichts laufen, ich denke da kommt so was
bei raus:
1
lade Adresse von foo in Register A
2
lade den Wert, der an der Adresse in Register A steht in Register B
3
lade Adresse von bar in register A
4
speichere den Wert in Register B in die Adresse in register A
Es hängt auch immer davon ab wie gut der Compiler optimiert. Wie gesagt
schau dir mal den Assemblercode an den der Compiler produziert. Du
brauchst nicht viel Assembler wissen, zum lesen und groben verstehen
reicht es wenn man die Befehlsreferenz für den Befehl kurz überfliegt.