it-swarm.com.de

Wächst der Stapel nach oben oder nach unten?

Ich habe diesen Code in c:

int q = 10;
int s = 5;
int a[3];

printf("Address of a: %d\n",    (int)a);
printf("Address of a[1]: %d\n", (int)&a[1]);
printf("Address of a[2]: %d\n", (int)&a[2]);
printf("Address of q: %d\n",    (int)&q);
printf("Address of s: %d\n",    (int)&s);

Die Ausgabe ist:

Address of a: 2293584
Address of a[1]: 2293588
Address of a[2]: 2293592
Address of q: 2293612
Address of s: 2293608

Ich sehe also, dass die Speicheradressen von a bis a[2] um jeweils 4 Bytes zunehmen. Aber von q zu s sinken die Speicheradressen um 4 Byte.

Ich frage mich zwei Dinge:

  1. Wächst der Stapel nach oben oder nach unten? (Es sieht für mich in diesem Fall wie beides aus)
  2. Was passiert zwischen a[2]- und q Speicheradressen? Warum gibt es dort einen großen Erinnerungsunterschied? (20 Bytes).

Hinweis: Dies ist keine Hausaufgabenfrage. Ich bin gespannt, wie Stack funktioniert. Danke für jede Hilfe.

76
hdn

Das Verhalten des Stacks (Aufwachsen oder Abwachsen) hängt von der binären Anwendungsschnittstelle (ABI) und der Organisation des Aufrufstapels (Aktivierungsdatensatz) ab. 

Ein Programm muss während seiner gesamten Lebensdauer mit anderen Programmen wie OS kommunizieren. ABI legt fest, wie ein Programm mit einem anderen Programm kommunizieren kann. 

Der Stack für verschiedene Architekturen kann in beide Richtungen wachsen, für eine Architektur ist er jedoch konsistent. Bitte überprüfen Sie diesen Wiki-Link. Das Wachstum des Stacks wird jedoch vom ABI dieser Architektur bestimmt. 

Wenn Sie beispielsweise die MIPS-ABI verwenden, wird der Aufrufstapel wie folgt definiert.

Betrachten wir die Funktion 'fn1' als 'fn2'. Nun sieht der Stack-Frame, wie er von 'fn2' gesehen wird, folgendermaßen aus:

direction of     |                                 |
  growth of      +---------------------------------+ 
   stack         | Parameters passed by fn1(caller)|
from higher addr.|                                 |
to lower addr.   | Direction of growth is opposite |
      |          |   to direction of stack growth  |
      |          +---------------------------------+ <-- SP on entry to fn2
      |          | Return address from fn2(callee) | 
      V          +---------------------------------+ 
                 | Callee saved registers being    | 
                 |   used in the callee function   | 
                 +---------------------------------+
                 | Local variables of fn2          |
                 |(Direction of growth of frame is |
                 | same as direction of growth of  |
                 |            stack)               |
                 +---------------------------------+ 
                 | Arguments to functions called   |
                 | by fn2                          |
                 +---------------------------------+ <- Current SP after stack 
                                                        frame is allocated

Jetzt können Sie sehen, wie der Stapel nach unten wächst. Wenn also die Variablen dem lokalen Rahmen der Funktion zugeordnet sind, werden die Adressen der Variablen tatsächlich nach unten gerichtet. Der Compiler kann die Reihenfolge der Variablen für die Speicherzuordnung festlegen. (In Ihrem Fall kann entweder 'q' oder 's' der Stapelspeicher zuerst zugewiesen werden. Im Allgemeinen führt der Compiler jedoch eine Stapelspeicherzuweisung gemäß der Reihenfolge der Deklaration der Variablen durch.

Im Falle der Arrays hat die Zuweisung jedoch nur einen einzelnen Zeiger, und der Speicher muss zugewiesen werden, und tatsächlich wird von einem einzelnen Zeiger darauf hingewiesen. Der Speicher muss für ein Array zusammenhängend sein. Obwohl der Stapel nach unten wächst, wächst der Stapel bei Arrays.

Das sind eigentlich zwei Fragen. Zum einen geht es darum, wie der Stapel wächst, wenn eine Funktion eine andere aufruft (wenn ein neuer Frame zugewiesen wird), und zum anderen darum, wie Variablen im Frame einer bestimmten Funktion angeordnet werden.

Beides wird vom C-Standard nicht spezifiziert, aber die Antworten sind etwas anders:

  • Auf welche Weise wächst der Stapel, wenn ein neuer Frame zugewiesen wird? Wenn die Funktion f() die Funktion g () aufruft, ist der Frame-Zeiger von f größer oder kleiner als der Rahmenzeiger von g? Dies kann in beide Richtungen gehen - es hängt vom jeweiligen Compiler und der Architektur ab (siehe "Aufrufkonvention"), aber ist es immer konsistent innerhalb einer bestimmten Plattform (mit ein paar bizarren Ausnahmen, siehe die Kommentare). Abwärts ist häufiger; Dies ist bei x86-, PowerPC-, MIPS-, SPARC-, EE- und Cell-SPUs der Fall.
  • Wie sind die lokalen Variablen einer Funktion in ihrem Stapelrahmen angeordnet? Dies ist nicht spezifiziert und völlig unvorhersehbar; Dem Compiler steht es frei, seine lokalen Variablen zu ordnen, er möchte jedoch das effizienteste Ergebnis erzielen.
43
Crashworks

Die Richtung, in der die Stapel wachsen, ist architekturspezifisch. Das heißt, ich verstehe, dass nur sehr wenige Hardwarearchitekturen Stacks haben, die erwachsen werden.

Die Richtung, in die ein Stapel wächst, ist unabhängig vom Layout eines einzelnen Objekts. Während also der Stapel kleiner wird, werden Arrays nicht (d. H. & Array [n] ist immer <& array [n + 1]);

13

Es gibt nichts in der Norm, das die Anordnung der Dinge auf dem Stapel bestimmt. Tatsächlich könnten Sie einen konformen Compiler erstellen, der keine Array-Elemente auf zusammenhängenden Elementen im Stack speichert, vorausgesetzt, er hatte die Möglichkeit, Array-Arithmetik immer noch ordnungsgemäß auszuführen (sodass er beispielsweise wusste, dass ein 1 war 1K von a [0] entfernt und konnte sich dafür einstellen).

Der Grund dafür, dass Sie zu unterschiedlichen Ergebnissen kommen, ist, dass das Array zwar ein Objekt ist und der Stack nach unten wächst, um "Objekte" hinzuzufügen, und dass er aufsteigende Array-Elemente in entgegengesetzter Reihenfolge aufweisen kann. Es ist jedoch nicht sicher, sich auf dieses Verhalten zu verlassen, da sich die Richtung ändern kann und Variablen aus verschiedenen Gründen ausgetauscht werden können. Dazu gehören unter anderem:

  • optimierung.
  • ausrichtung.
  • die Launen der Person der Stack-Management-Teil des Compilers.

hier für meine ausgezeichnete Abhandlung über die Stapelrichtung :-)

Antworten auf Ihre spezifischen Fragen:

  1. Wächst der Stapel nach oben oder nach unten?
    Es spielt keine Rolle (in Bezug auf den Standard), aber da Sie gefragt haben, kann es je nach Implementierung oder im Speicher nach unten wachsen.
  2. Was passiert zwischen einer [2] - und einer q-Speicheradresse? Warum gibt es dort einen großen Erinnerungsunterschied? (20 Bytes)?
    Es spielt keine Rolle (in Bezug auf den Standard). Siehe oben für mögliche Gründe.
4
paxdiablo

Bei einem x86 besteht die "Speicherzuordnung" eines Stapelrahmens einfach aus dem Subtrahieren der erforderlichen Anzahl von Bytes vom Stapelzeiger (ich glaube, dass andere Architekturen ähnlich sind). In diesem Sinne denke ich, dass der Stapel "nach unten" wächst, indem die Adressen schrittweise kleiner werden, je tiefer Sie in den Stapel gehen (ich stelle mir jedoch immer vor, dass der Speicher oben links mit 0 beginnt und bei Bewegung größere Adressen erhält nach rechts und einwickeln, so wächst der Stapel in meinem geistigen Bild ...). Die Reihenfolge der deklarierten Variablen hat möglicherweise keinen Einfluss auf ihre Adressen. Ich glaube, der Standard erlaubt es dem Compiler, sie neu zu ordnen, solange sie keine Nebenwirkungen verursacht (jemand korrigiert mich bitte, wenn ich falsch liege). . Sie stecken einfach irgendwo in dieser Lücke in den verwendeten Adressen, wenn sie die Anzahl der Bytes vom Stack-Zeiger abziehen.

Die Lücke um das Array herum mag eine Art Polsterung sein, aber es ist mir geheimnisvoll.

2
rmeador

Zuallererst werden die 8 Byte des nicht verwendeten Speicherplatzes (nicht 12, erinnern Sie sich an den Stack) nach unten. Der nicht zugewiesene Speicherplatz ist also von 604 bis 597. und warum? ... Da jeder Datentyp Speicherplatz benötigt, beginnend mit der durch seine Größe teilbaren Adresse. In unserem Fall benötigt ein Array von 3 Ganzzahlen 12 Byte Speicherplatz, und 604 ist nicht durch 12 teilbar. Es bleiben also leere Felder übrig, bis es auf eine Speicheradresse stößt, die durch 12 teilbar ist, also 596. 

Der dem Array zugewiesene Speicherplatz reicht also von 596 bis 584. Da die Arrayzuweisung jedoch fortgesetzt wird, beginnt das erste Element des Arrays mit der Adresse 584 und nicht mit der Adresse 596.

1
Prateek Khurana

wächst nach unten und dies liegt an dem Standard für die Reihenfolge der Little-Endian-Bytes, wenn es um die Datenmenge im Speicher geht.

Eine Möglichkeit, wie Sie es betrachten können, ist, dass der Stapel nach oben wächst, wenn Sie den Speicher von 0 von oben und den Maximalwert von unten betrachten.

Der Grund für das Abwachsen des Stapels ist, dass er aus der Perspektive des Stapels oder des Basiszeigers dereferenziert werden kann.

Denken Sie daran, dass die Dereferenzierung eines beliebigen Typs von der niedrigsten zur höchsten Adresse zunimmt. Da der Stack nach unten wächst (höchste bis niedrigste Adresse), können Sie den Stack wie einen dynamischen Speicher behandeln.

Dies ist ein Grund, warum so viele Programmier- und Skriptsprachen eine stapelbasierte virtuelle Maschine anstelle einer registergestützten verwenden.

1
Nergal

Der Compiler kann lokale (Auto-) Variablen an beliebiger Stelle des lokalen Stack-Frames zuweisen. Daraus lässt sich nicht zuverlässig ableiten, aus welcher Richtung der Stack wächst. Sie können die Richtung des Stack-Wachstums ablesen, indem Sie die Adressen verschachtelter Stack-Frames vergleichen, dh die Adresse einer lokalen Variablen innerhalb des Stack-Frames einer Funktion im Vergleich zu ihrem klingenden Verhalten vergleichen:

#include <stdio.h>
int f(int *x)
{
  int a;
  return x == NULL ? f(&a) : &a - x;
}

int main(void)
{
  printf("stack grows %s!\n", f(NULL) < 0 ? "down" : "up");
  return 0;
}
1
matja

Das hängt von der Architektur ab. Um Ihr eigenes System zu überprüfen, verwenden Sie diesen Code aus GeeksForGeeks :

// C program to check whether stack grows 
// downward or upward. 
#include<stdio.h> 

void fun(int *main_local_addr) 
{ 
    int fun_local; 
    if (main_local_addr < &fun_local) 
        printf("Stack grows upward\n"); 
    else
        printf("Stack grows downward\n"); 
} 

int main() 
{ 
    // fun's local variable 
    int main_local; 

    fun(&main_local); 
    return 0; 
} 
0
kurdtpage

Ich denke nicht, dass es so deterministisch ist. Das Array a scheint zu "wachsen", da dieser Speicher zusammenhängend zugewiesen werden sollte. Da jedoch q und s überhaupt nicht miteinander verwandt sind, legt der Compiler sie einfach an einer beliebigen freien Speicherstelle im Stapel ab, wahrscheinlich an einer Stelle, die für eine Ganzzahl am besten geeignet ist.

Was zwischen a [2] und q passiert ist, ist, dass der Platz um q herum nicht groß genug war (dh nicht größer als 12 Byte), um ein 3-Integer-Array zuzuweisen.

0
javanix

Dies hängt von Ihrem Betriebssystem und Ihrem Compiler ab.

0
David R Tribble

Mein Stack scheint sich auf Adressen mit geringerer Nummerierung zu erstrecken.

Es kann auf einem anderen Computer anders sein oder sogar auf meinem eigenen Computer, wenn ich einen anderen Compileraufruf verwende. ... oder der Compiler muss keinen Stack verwenden (alles inline (Funktionen und Variablen, wenn ich die Adresse nicht genommen habe)).

$ cat stack.c
#include <stdio.h>

int stack(int x) {
  printf("level %d: x is at %p\n", x, (void*)&x);
  if (x == 0) return 0;
  return stack(x - 1);
}

int main(void) {
  stack(4);
  return 0;
}
 $/usr/bin/gcc -Wall -Wextra -std = c89 -pedantic stack.c 
$ ./a.out
level 4: x ist um 0x7fff7781190c 
 Ebene 3: x ist um 0x7fff778118ec 
 Ebene 2: x ist um 0x7fff778118cc 0: x ist bei 0x7fff7781188c 
0
pmg

Der Stapel wächst (auf x86). Der Stapel wird jedoch in einem Block zugewiesen, wenn die Funktion geladen wird, und Sie können nicht garantieren, in welcher Reihenfolge die Artikel auf dem Stapel liegen.

In diesem Fall wurde Platz für zwei Ints und ein Drei-Int-Array auf dem Stapel reserviert. Es hat auch zusätzliche 12 Bytes nach dem Array zugewiesen, daher sieht es so aus:

a [12 Bytes]
Auffüllen (?) [12 Bytes]
s [4 Bytes]
q [4 Byte] 

Aus irgendeinem Grund entschied Ihr Compiler, dass er 32 Bytes für diese Funktion und möglicherweise mehr zuweisen muss. Das ist für Sie als C-Programmierer undurchsichtig, Sie wissen nicht warum.

Wenn Sie wissen wollen, warum, kompilieren Sie den Code in Assemblersprache. Ich glaube, es ist -S auf gcc und/S auf MS C-Compiler. Wenn Sie sich die Eröffnungsanweisungen zu dieser Funktion ansehen, werden der alte Stapelzeiger gespeichert und dann 32 (oder etwas anderes!) Davon abgezogen. Von dort aus können Sie sehen, wie der Code auf diesen 32-Byte-Speicherblock zugreift, und herausfinden, was Ihr Compiler macht. Am Ende der Funktion sehen Sie, wie der Stapelzeiger wiederhergestellt wird.

0
Aric TenEyck

Stack wächst nach unten. Also f (g (h ()))), beginnt der für h zugewiesene Stapel an einer niedrigeren Adresse als g und g ist niedriger als f. Variablen innerhalb des Stacks müssen jedoch der C-Spezifikation folgen.

http://c0x.coding-guidelines.com/6.5.8.html

1206 Wenn die Objekte, auf die gezeigt wird, Mitglieder desselben Aggregatsobjekts sind, vergleichen Zeiger auf später deklarierte Strukturelemente mehr als Zeiger mit zuvor deklarierten Elementen in der Struktur, und Zeiger auf Arrayelemente mit größeren Indexwerten vergleichen größere Zeiger mit Elementen derselben Struktur Array mit niedrigeren Indexwerten.

& a [0] <& a [1] muss immer wahr sein, unabhängig davon, wie 'a' zugeordnet wird 

0
user1187902