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