Wenn Sie lokale Variablen in einem C-Bereich zuweisen (z. B. Funktionen), haben diese keinen Standardinitialisierungscode (z. B. C ++ - Konstruktoren). Und da sie nicht dynamisch zugewiesen werden (sie sind nur nicht initialisierte Zeiger), müssen keine zusätzlichen (und möglicherweise teuren) Funktionen aufgerufen werden (z. B. malloc), um sie vorzubereiten / zuzuweisen.
Aufgrund der Funktionsweise des Stapels bedeutet das Zuweisen einer Stapelvariablen einfach das Dekrementieren des Stapelzeigers (dh das Erhöhen der Stapelgröße, da sie bei den meisten Architekturen nach unten wächst), um Platz dafür zu schaffen. Aus Sicht der CPU bedeutet dies, dass ein einfacher SUB-Befehl ausgeführt wird: SUB rsp, 4(falls Ihre Variable 4 Byte groß ist - beispielsweise eine reguläre 32-Bit-Ganzzahl).
Wenn Sie mehrere Variablen deklarieren, ist Ihr Compiler außerdem intelligent genug, um sie tatsächlich zu einer großen SUB rsp, XXAnweisung zusammenzufassen, wobei XXdie Gesamtgröße der lokalen Variablen eines Bereichs angegeben wird. In der Theorie. In der Praxis passiert etwas anderes.
In solchen Situationen ist GCC Explorer für mich ein unschätzbares Werkzeug, um (mit enormer Leichtigkeit) herauszufinden, was "unter der Haube" des Compilers passiert.
Schauen wir uns also an, was passiert, wenn Sie tatsächlich eine Funktion wie die folgende schreiben: GCC-Explorer-Link .
C-Code
int function(int a, int b) {
int x, y, z, t;
if(a == 2) { return 15; }
x = 1;
y = 2;
z = 3;
t = 4;
return x + y + z + t + a + b;
}
Resultierende Montage
function(int, int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-20], edi
mov DWORD PTR [rbp-24], esi
cmp DWORD PTR [rbp-20], 2
jne .L2
mov eax, 15
jmp .L3
.L2:
-- snip --
.L3:
pop rbp
ret
Wie sich herausstellt, ist GCC noch schlauer. Der SUB-Befehl zum Zuweisen der lokalen Variablen wird überhaupt nicht ausgeführt. Es wird nur (intern) davon ausgegangen, dass der Speicherplatz "belegt" ist, es werden jedoch keine Anweisungen zum Aktualisieren des Stapelzeigers hinzugefügt (z SUB rsp, XX. B. ). Dies bedeutet, dass der Stapelzeiger nicht auf dem neuesten Stand gehalten wird. Da in diesem Fall jedoch keine PUSHAnweisungen mehr ausgeführt werden (und keine rsprelativen Suchvorgänge durchgeführt werden), nachdem der Stapelspeicher verwendet wurde, gibt es kein Problem.
Hier ist ein Beispiel, in dem keine zusätzlichen Variablen deklariert sind: http://goo.gl/3TV4hE
C-Code
int function(int a, int b) {
if(a == 2) { return 15; }
return a + b;
}
Resultierende Montage
function(int, int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov DWORD PTR [rbp-8], esi
cmp DWORD PTR [rbp-4], 2
jne .L2
mov eax, 15
jmp .L3
.L2:
mov edx, DWORD PTR [rbp-4]
mov eax, DWORD PTR [rbp-8]
add eax, edx
.L3:
pop rbp
ret
Wenn Sie sich den Code vor der vorzeitigen Rückgabe ansehen ( jmp .L3die zum Bereinigungs- und Rückgabecode springt), werden keine zusätzlichen Anweisungen aufgerufen, um die Stapelvariablen "vorzubereiten". Der einzige Unterschied besteht darin, dass die Funktionsparameter a und b, die in den Registern ediund esigespeichert sind, an einer höheren Adresse als im ersten Beispiel ( [rbp-4]und [rbp - 8]) auf den Stapel geladen werden . Dies liegt daran, dass für die lokalen Variablen wie im ersten Beispiel kein zusätzlicher Speicherplatz "zugewiesen" wurde. Wie Sie sehen können, ist der einzige "Overhead" für das Hinzufügen dieser lokalen Variablen eine Änderung eines Subtraktionsterms (dh nicht einmal das Hinzufügen einer zusätzlichen Subtraktionsoperation).
In Ihrem Fall fallen also praktisch keine Kosten für die einfache Deklaration von Stapelvariablen an.