Stack und Headspeicher

joner

Grünschnabel
Hallo, da ist der Joner wieder :) ,


Die Grundlagen des stack und Heap kenn ich eigt, aber würde mich gerne verbessern ;), Bei dynamischer Speicherreservierung muss man ja, den Platz erstmal reservieren und wieder freigeben , und Diese erfolgt mit einem Zeiger * und "sizeof"- Funktion, aber wie ist es beim Stack, wird der Speicherplatz automatisch reserviert ? und somit bleibt der Inhalt des Stack während des Gesamten Programmablauf bestehen. Grob gesagt ist das alles, was ich weiss, könnte einer der Profis kurz, wenn möglich, mit einfachen Beispielen erklären , wieso man das alles braucht ? (Mein Wissen über Stack ist leider nicht viel und ich kann es nicht verstehen, warum man überhaupt Stack braucht) :(


Danke MFG
 
Zuletzt bearbeitet:
Der Stack enthält meines Wissens nach die noch hardwarenäheren Anweisungen, die der Prozessor der Reihe nach abarbeitet. Normalerweise wird der Stack aber automaitsch generiert und auch der Speicher automatisch alloziert. Passt man aber bei den selbst definierten Variablen nicht auf (begrenzt deren Inhalt nicht), kann ein geschickter Hacker den Stack überschreiben und das Programm zum Absturz bringen.

Anmerkung: Kenne mich auch nicht aus, habe nie C programmiert :p
 
Head? Du meinst Heap, oder?

Ja, der Stack wird automatisch allokiert.
Variablen, die auf dem Stack liegen, werden automatisch freigegeben, sobald sie aus dem Scope sind.

Der Stack ist schneller als der Heap, allerdings nicht für grössere Datenmengen geeignet.

Vor allem aber ist der Stack statisch (ausser in C99), der Heap dynamisch. Wenn du nicht genau weisst, wie viel Speicher du zur Laufzeit brauchst, kannst du im Heap zur Laufzeit Speicher reservieren. Im Stack geht das nicht.

Einige weitere Punkte findest du hier: http://stackoverflow.com/questions/79923/what-and-where-are-the-stack-and-heap

Gruss
cwriter
 
Zuletzt bearbeitet:
Hallo joner

Zwischen Stack- und Heapspeicher gibt es hardwaretechnisch auf normalen Maschinen keinen Unterschied. Beides sind einfach Regionen in deinem virtuellen Adressraum. Bezüglich der Allokation des Stacks sei so viel gesagt: Dies wird mehr oder weniger durch den Compiler bereits erledigt indem im Prolog der Funktion den Stackpointer um die Anzahl Bytes verringert die für lokale Variabeln benötigt werden. Dadurch ist sichergestellt dass unabhängig davon wie die aktuelle Implementation der Hardware für die Bewegung des Stackpointers ist der Block zwischen dem alten Stackpointer und dem neuen (also genau die Anzahl Bytes für die lokalen Variabeln) alloziert ist. Im Windows Kernel ist das ganze eigentlich so implementiert dass jeder erstellte Thread wenn er erstellt wird vom System einen Bereich im Speicher zugewiesen bekommt den er als Stack verwenden kann.

Das gibt den typischen Prolog und Epilog zum Beispiel für diese Funktion (die nicht wirklich Sinn macht und auch nur ohne Optimierer diesen Output generieren würde):
C++:
int __stdcall foo(int a1, int a2) {
    int a1Old = a1;
    return a1Old - a2;
}

-> Output:
Code:
// prolog: 
// ebp sichern
push ebp
// den alten stackzeiger nach ebp verschieben
mov ebp, esp
// Stackzeiger um 4 nach unten verschieben weil 1 lokale Variable von 4 bytes
sub esp, 4
// Jetzt ist: 
// [ebp] == alter Wert von ebp
// [ebp + 4] == rücksprungadresse
// [ebp + 8] == ersters argument
// [ebp + 12] == zweites Argument
// [ebp - 4] == [esp] == lokale variable

mov ebx, [ebp + 8]
mov [ebp - 4], ebx
mov eax, [ebp - 4]
sub eax, [ebp + 12]

// Epilog
// lokale Variable wieder "entfernen" indem der Stackzeiger wieder zurückbewegt wird
add esp, 4 // Auch möglich: mov esp, ebp, damit wäre er auch wieder am alten Ort
// alten Wert wieder zurückholen 
pop ebp
retn 8

Jetzt schauen wir uns das ganze mal mit Speicher an und vergleichen "auf dem Heap" mit "auf dem Stack" um auch solche Fragen wie "was ist performanter?" beantworten zu können.

Mit malloc auf dem Heap:
C++:
int foo(int a1, int a2) {
    int* pOld = (int*)malloc(sizeof(int));
    *pOld = a1;
    return *pOld - a2;
}

In einer Welt in der malloc als __stdcall deklariert und implementiert ist und absolut kein Optimierer vorhanden ist sondern einfach der Code 1:1 übersetzt wird wäre das dann folgendermassen:
Code:
push ebp
mov ebp, esp
sub esp, 4

push 4
call malloc
mov [ebp - 4], eax
mov eax, [ebp - 4]
mov ebx, [ebp + 8]
mov [eax], ebx
mov eax, [ebp - 4]
mov eax, [eax]
sub eax, [ebp + 12]

add esp, 4
pop ebp
retn 8

Ohne malloc auf dem Stack:
C++:
int foo(int a1, int a2) {
   int tmpValue = 0;
   int* pOld = &tmpValue;
   *pOld = a1;
    return *pOld - a2;
}

Output:
Code:
push ebp
mov ebp, esp
sub esp, 8

xor eax, eax
mov [ebp - 4], eax
lea ebx, ebp - 4
mov [ebp - 8], ebx
mov eax, [ebp - 8]
mov ebx, [ebp + 8]
mov [eax], ebx
mov eax, [ebp - 8]
mov eax, [eax]
sub eax, [ebp + 12]

add esp, 8
pop ebp
retn 8


Wir sehen also dass es eigentlich faktisch gesehen keinen Unterschied gibt zwischen den zwei Varianten. Der einzige Overhead der jetzt in diesem Fall stattfindet ist dass malloc aufgerufen werden muss, aber das ist ja auch zu erwarten, schliesslich rufen wir es ja auch in der Funktion auf. Stack und Heap unterscheiden sich weder in der Verwendung noch in der Implementation noch in der Geschwindigkeit.

@cwriter:
Der Stack ist nicht schneller als der Heap ausser für die Allokation und Deallokation. Hier reden wir allerdings von Overheads die im Bereich der Mikrooptimierung wandeln. Und es gibt selten eine Anwendung die schon soweit optimiert ist dass es Sinn macht mit Mikrooptimierungen zu beginnen.

Ausserdem ist die Wortwahl "Variabeln [...] auf dem Stack [...] werden automatisch freigegeben, sobald sie aus dem Scope sind." etwas fehlleitend. Der Compiler fügt einen Funktionsaufruf an den Destruktor des Typs (!) der Variabeln (für eine loake Variable vom Typ Foo* wird nicht Foo::~Foo aufgerufen!) ein, mehr nicht. Der Speicher des Stacks bleibt weiterhin bestehen und es wird nichts freigegeben.

Und eine letzte Bemerkung zu C99. Auch da ist der Stack statisch und nur der Heap dynamisch. Ich denke du sprichst VLAs an wegen C99. Diese werden ganz normal auf dem Heap alloziert und es handelt sich dabei nur um syntactical sugar, fast wie ein smart pointer in C++. Allokation und Freigabe ist da nicht dein Problem.

Grüsse
Cromon
 
Zurück