wenn ich innerhalb einer C-Funktion drei Variablen
deklariere:
int a,b,c
dann kann ich ja mit denen machen, was ich will.
ob ich nun sage:
c++;a++; b++;
oder
b++;a++;c++
ist egal.
Ich habe also wärend des Programmierens quasi wahlfreien Zugriff auf die
Variablen innherhalb der Funktion.
Automatische Variablen werden ja auf dem Stack abgelegt. Wenn nun im
Programm kommt:
c++;
und c ist nicht ganz oben auf dem Stack, wie wird dann auf c
zugegriffen?
Macht der Herr Compiler das in Maschinensprache etwa so:
"Der Will c ändern.
Hmm, c ist auf dem Stack an der 3. Position.
Da nehm ich erstmal zwei runter und speicher die, hmm, keine Ahnung,
inne Ecke?
Gut, nun liegt c oben. Les ich aus in ein Register R für späteres
bearbeiten des Wertes. Nun pack ich den gelesenen Wert wieder auf den
Stack.
Noch den Kram aus der Ecke rauf."
c ist noch nicht geändert.
Nun stelle ich eine berechnung an mit R (das für den Wert von C steht).
Das Ergebnis in R wird mit ähnlicher Methodik von vorhin in c
geschrieben.
Wobei die "ECKE" ein vom Compiler reservierter Speicherbereich ist?
Wie groß wird denn so eine "ECKE" wenn man den Speicherbedarf für
Controller abschätzen muss?
Theoretisch müsste die ECKE ja genauso groß sein wie die (Anzahl lokaler
variablen)-1, damit genug Platz zum rumpacken ist. Wird die ECKE
dynamisch verwaltet? (Auf nem 8-Bitter wohl eher nicht?).
Du hast den Stack nicht ganz verstanden. Der Stackpointer zeigt zwar
immer auf das "oberste" Datum, so dass neue Daten "oben drauf"
gespeichert werden können, trotzdem kann auf alle Daten "darunter"
zugegriffen werden.
Der Compiler weiß, dass c über [Stackpointer + 8] erreichbar ist, weil
davor noch 2 * 4 Byte für a und b reserviert sind. (Ich gehe davon aus,
dass ein Integer 32 Bit hat und der SP nach unten wächst)
Die Sache mit der Ecke ist reichlich ineffektiv.Wie genau es gemacht
wird,hängt sicher auch vom Compiler und den Optimierungsgraden ab.Es wär
sicherlich vorstellbar,das in einigen Fällen der Speicherzugriff ganz
wegoptimiert und ein Register verwendet wird.
Ansonsten wird ein Zeiger relativ zum Stackpointer benutzt.Dazu wird der
Stackpointer zu Beginn der Funktion gesichert und alle lokalen Variablen
sind dann Offsets zu diesem Wert.Damit können dann auch push/pop zum
sichern von Daten oder weitere Funktionsaufrufe genutzt werden,ohne das
etwas verloren geht.Dadurch ist es beispielsweise möglich,das eine
Funktion sich selbst aufruft.
Beim x86 (PC) gibt es sogar ein extra Register um den Stackpointer für
lokale Variablen zu sichern (BP Base Pointer)
Super erklärung, Danke euch!
Den Stack hatte ich also nicht verstanden, weil ich vom unflexiblen
stack in nem 8-Bitter auch auf den PC geschlossen hatte.
Das Grundprinzip des Stacks ist immer das selbe,ein Zeiger-Register wird
intern benutzt um nach dem FIFO-Prinzip (First In-First Out) Daten
temporär zu sichern.Mir fällt jetzt auf Anhieb keine Architektur ein,wo
der Stack nicht nach unten (von höheren zu niederen Adressen hin)
wächst.
Ob die Architektur nun noch ein zusätzliches Register bereitstellt,um
den Stack indirekt zu adressieren hängt davon ab,wie gut sie auf
Hochsprachen optimiert ist.Aber meistens spricht nix dagegen,sich
einfach auf ein Register zu einigen und diese selbst erstellte
Konvention dann konsequent durchzuziehen.Einen Stackpointer der von der
Hardware verwaltet wird,gibt es eigentlich immer,schliesslich müssen ja
bei Interrupts und Funktionsaufrufen auch Sachen auf dem Stack gesichert
werden.
> Den Stack hatte ich also nicht verstanden, weil ich vom unflexiblen> stack in nem 8-Bitter auch auf den PC geschlossen hatte.
Der Stack ist auch auf einem 8-Bitter nicht unflexibel.
Was du die ganze Zeit ausser acht lässt ist, dass der
Stack in ganz gewöhnlichem Ram residiert. Der Stack ist
also eine Datenstruktur, die logisch gesehen etwas
höher anzusiedeln ist als reine Speicherzugriffe. Aber
da der Stack im RAM realisiert wird, ist es selbstverständlich
möglich mittels ganz normaler Speicherzugriffe auf das
RAM und damit auf den Stack zuzugreifen (*). Alles was man
dazu braucht ist die aktuelle Position des obersten
Elements im Stack. Das ist die Basisadresse von der
weg gerechnet wird. Wenn ich die habe, kann ich immer
mittels gewöhnlicher Speicherzugriffe auf den Stack
zugreifen. Das unterscheidet sich konzeptionell in nichts
von zb. einem Zugriff auf ein bestimmtes Element eines
Arrays. Auch dort kenne ich nur die erste Adresse des
Arrayelementes und kann daraus berechnen, wo das Element
mit dem Index i im Speicher liegt. Es läuft immer auf
dasselbe hinaus: Wenn ich eine Adresse im Speicher kenne,
und weis wo in Relation dazu die gesuchten Daten gespeichert
sind, kann ich deren Speicheradresse ausrechnen und den
Speicherzugriff machen. Manche Prozessoren haben halt dafür
noch spezielle Befehle bzw. spezielle Register. Das kann
so sein, muss aber nicht. Durch das Vorhandensein dieser
Spezialitäten wird nichts an Funktionsumfang gewonnen.
Einzig die Art und Weise wie der Zugriff gemacht werden kann,
bzw. wie schnell er gemacht werden kann ist anders. Aber
Möglichkeiten hab ich deswegen auch nicht mehr.
(*) Von Assembler aus. Der Compiler darf das. Als Hochsprachen-
programmierer wird dir einiges von den Möglichkeiten genommen
um zu verhindern, dass du gegen den Compiler programmierst.
@Frank:
Natürlich hast du Recht.Das letzte Element was "oben auf" gelegt
wird,ist zugleich das erste,was wieder abgenommen wird.Ab einer gewissen
Zeit sollte man einfach schlafen ;)
Okay, dann habe ich es jetzt verstanden :)
In nem PIC war der Stack ne eigene Hardware und nicht im Ram.
Hab mir jetzt aber auch lieber nen Mega8 geholt.