Groeit de stapel omhoog of omlaag?

Ik heb dit stukje 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);

De uitvoer is:

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

Dus ik zie dat van atot a[2]de geheugenadressen elk met 4 bytes toenemen.
Maar van qtot snemen de geheugenadressen met 4 bytes af.

Ik vraag me twee dingen af:

  1. Groeit de stapel omhoog of omlaag? (In dit geval lijkt het me beide)
  2. Wat gebeurt er tussen a[2]en qgeheugenadressen? Waarom is er een groot geheugenverschil daar? (20 bytes).

Opmerking: dit is geen huiswerkvraag. Ik ben benieuwd hoe stack werkt. Bedankt voor alle hulp.


Antwoord 1, autoriteit 100%

Het gedrag van de stapel (opgroeien of opgroeien) hangt af van de binaire interface (ABI) van de toepassing en hoe de aanroepstack (ook wel activatierecord genoemd) is georganiseerd.

Gedurende zijn hele levensduur moet een programma communiceren met andere programma’s, zoals het besturingssysteem. ABI bepaalt hoe een programma kan communiceren met een ander programma.

De stapel voor verschillende architecturen kan hoe dan ook groeien, maar voor een architectuur zal deze consistent zijn. Controleer a.u.b. dezewiki-link. Maar de groei van de stapel wordt bepaald door de ABI van die architectuur.

Als u bijvoorbeeld de MIPS ABI neemt, wordt de call-stack als volgt gedefinieerd.

Laten we eens kijken naar de functie ‘fn1’ die ‘fn2’ aanroept. Nu is het stapelframe zoals gezien door ‘fn2’ als volgt:

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

Nu kun je zien dat de stapel naar beneden groeit. Dus als de variabelen worden toegewezen aan het lokale frame van de functie, groeien de adressen van de variabele eigenlijk naar beneden. De compiler kan beslissen over de volgorde van variabelen voor geheugentoewijzing. (In uw geval kan het ofwel ‘q’ of ‘s’ zijn dat als eerste stackgeheugen wordt toegewezen. Maar over het algemeen doet de compiler de geheugentoewijzing in de volgorde van de declaratie van de variabelen).

Maar in het geval van de arrays heeft de toewijzing slechts een enkele aanwijzer en het geheugen dat moet worden toegewezen, wordt feitelijk door een enkele aanwijzer aangewezen. Het geheugen moet aaneengesloten zijn voor een array. Dus hoewel de stapel naar beneden groeit, groeit de stapel voor arrays.


Antwoord 2, autoriteit 58%

Dit zijn eigenlijk twee vragen. Een daarvan gaat over de manier waarop de stapel groeit wanneer de ene functie een andere aanroept(wanneer een nieuw frame wordt toegewezen) , en de andere gaat over hoe variabelen zijn ingedeeld in het frame van een bepaalde functie.

Geen van beide wordt gespecificeerd door de C-standaard, maar de antwoorden zijn een beetje anders:

  • Op welke manier groeit de stapel wanneer een nieuw frame wordt toegewezen — als functie f() functie g() aanroept, zal de framepointer van fgroter of kleiner zijn dan g‘s frame pointer?Dit kan alle kanten op — het hangt af van de specifieke compiler en architectuur (zoek “calling convention”), maar het is altijd consistent binnen een bepaald platform(op enkele bizarre uitzonderingen na, zie de opmerkingen). Naar beneden komt vaker voor; dit is het geval in x86, PowerPC, MIPS, SPARC, EE en de Cell SPU’s.
  • Hoe zijn de lokale variabelen van een functie ingedeeld in het stapelframe?Dit is niet gespecificeerd en volledig onvoorspelbaar; de compiler is vrij om zijn lokale variabelen te ordenen, maar hij wil het meest efficiënte resultaat krijgen.

Antwoord 3, autoriteit 17%

De richting waarin stapels groeien, is specifiek voor de architectuur. Dat gezegd hebbende, heb ik begrepen dat slechts een paar hardware-architecturen stapels hebben die opgroeien.

De richting waarin een stapel groeit, is onafhankelijk van de lay-out van een afzonderlijk object. Dus hoewel de stapel kleiner wordt, zullen arrays dat niet doen (d.w.z. &array[n] zal altijd < &array[n+1] zijn);


Antwoord 4, autoriteit 5%

Er is helemaal niets in de standaard dat bepaalt hoe dingen op de stapel worden georganiseerd. In feite zou je een conforme compiler kunnen bouwen die helemaal geen array-elementen op aaneengesloten elementen op de stapel opslaat, op voorwaarde dat hij de slimheid had om nog steeds de rekenkunde van array-elementen correct uit te voeren (zodat hij bijvoorbeeld wist dat a1was 1K verwijderd van a[0] en zou daarvoor kunnen corrigeren).

De reden dat u verschillende resultaten krijgt, is dat, hoewel de stapel kan groeien om er “objecten” aan toe te voegen, de array een enkel “object” is en oplopende array-elementen in de tegenovergestelde volgorde kan hebben. Maar het is niet veilig om op dat gedrag te vertrouwen, aangezien de richting kan veranderen en variabelen om verschillende redenen kunnen worden verwisseld, waaronder, maar niet beperkt tot:

  • optimalisatie.
  • uitlijning.
  • de grillen van de persoon die het stackbeheergedeelte van de compiler is.

Zie hiervoor mijn uitstekende verhandeling over stapelrichting 🙂

Als antwoord op uw specifieke vragen:

  1. Groeit de stapel omhoog of omlaag?
    Het maakt helemaal niet uit (in termen van de standaard), maar aangezien je het vroeg, kan het opgroeien ofin het geheugen, afhankelijk van de implementatie.
  2. Wat gebeurt er tussen a[2] en q geheugenadressen? Waarom is er een groot geheugenverschil daar? (20 bytes)?
    Het maakt helemaal niet uit (in termen van de standaard). Zie hierboven voor mogelijke redenen.

Antwoord 5, autoriteit 4%

Op een x86 bestaat de geheugentoewijzing van een stapelframe eenvoudigweg uit het aftrekken van het benodigde aantal bytes van de stapelaanwijzer (ik geloof dat andere architecturen vergelijkbaar zijn). In die zin denk ik dat de stapel “naar beneden” groeit, in die zin dat de adressen steeds kleiner worden naarmate je dieper in de stapel roept (maar ik stel me altijd voor dat het geheugen begint met 0 in de linkerbovenhoek en grotere adressen krijgt als je beweegt naar rechts en naar beneden wikkelen, dus in mijn mentale beeld groeit de stapel…). De volgorde van de variabelen die worden gedeclareerd, heeft mogelijk geen invloed op hun adressen – ik geloof dat de standaard de compiler toestaat om ze opnieuw te ordenen, zolang het geen bijwerkingen veroorzaakt (corrigeer me alstublieft als ik het mis heb) . Ze zitten gewoon ergens in dat gat in de gebruikte adressen die worden gemaakt wanneer het het aantal bytes van de stapelaanwijzer aftrekt.

De opening rond de array kan een soort opvulling zijn, maar het is mysterieus voor mij.


Antwoord 6

Het hangt af van uw besturingssysteem en uw compiler.


Antwoord 7

Allereerst zijn 8 bytes aan ongebruikte ruimte in het geheugen (het is geen 12, onthoud dat de stapel naar beneden groeit, dus de ruimte die niet is toegewezen is van 604 tot 597). en waarom?.
Omdat elk gegevenstype ruimte in beslag neemt in het geheugen, te beginnen met het adres dat deelbaar is door zijn grootte. In ons geval neemt een array van 3 gehele getallen 12 bytes geheugenruimte in beslag en 604 is niet deelbaar door 12. Dus het laat lege ruimtes achter totdat het een geheugenadres tegenkomt dat deelbaar is door 12, het is 596.

Dus de geheugenruimte die aan de array is toegewezen is van 596 tot 584. Maar aangezien de array-toewijzing doorgaat, begint het eerste element van de array dus vanaf adres 584 en niet vanaf 596.


Antwoord 8

groeit naar beneden en dit komt door de kleine endian-bytevolgordestandaard als het gaat om de set gegevens in het geheugen.

Eén manier waarop je ernaar zou kunnen kijken, is dat de stapel WEL naar boven groeit als je naar het geheugen kijkt vanaf 0 van boven en max van onder.

De reden waarom de stapel naar beneden groeit, is om te kunnen derefereren vanuit het perspectief van de stapel of de basisaanwijzer.

Houd er rekening mee dat het dereferentie van elk type toeneemt van het laagste naar het hoogste adres. Aangezien de stapel naar beneden groeit (van het hoogste naar het laagste adres), kunt u de stapel behandelen als dynamisch geheugen.

Dit is een van de redenen waarom zoveel programmeer- en scripttalen een op stapels gebaseerde virtuele machine gebruiken in plaats van een op registers gebaseerde.


Antwoord 9

Het hangt af van de architectuur. Gebruik deze code van GeeksForGeeksom uw eigen systeem te controleren. :

// 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; 
} 

Antwoord 10

Het staat de compiler vrij om lokale (auto)variabelen toe te wijzen aan elke plaats in het lokale stackframe, je kunt de groeirichting van de stack alleen daaruit niet op betrouwbare wijze afleiden. U kunt de richting van de stapelgroei afleiden uit het vergelijken van de adressen van geneste stapelframes, dwz het vergelijken van het adres van een lokale variabele binnen het stapelframe van een functie in vergelijking met zijn callee :

#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;
}

Antwoord 11

Ik denk niet dat het zo deterministisch is. De a-array lijkt te “groeien” omdat dat geheugen aaneengesloten moet worden toegewezen. Omdat q en s echter helemaal niets met elkaar te maken hebben, plaatst de compiler ze allemaal op een willekeurige vrije geheugenlocatie in de stapel, waarschijnlijk degene die het beste bij een geheel getal passen.

Wat er tussen a[2] en q is gebeurd, is dat de ruimte rond de locatie van q niet groot genoeg was (dwz niet groter dan 12 bytes) om een ​​array van 3 gehele getallen toe te wijzen.


Antwoord 12

Mijn stapel lijkt zich uit te breiden naar lager genummerde adressen.

Het kan anders zijn op een andere computer, of zelfs op mijn eigen computer als ik een andere compiler-aanroep gebruik. … of de compiler heeft ervoor gekozen om helemaal geen stapel te gebruiken (alles inline (functies en variabelen als ik het adres niet heb overgenomen)).

$ 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
niveau 4: x is op 0x7fff7781190c 
Niveau 3: X is op 0x7fff778118EC
Niveau 2: X is op 0x7fff778118cc
Niveau 1: X is op 0x7fff778118AC
Niveau 0: X is op 0x7fff7781188C

Antwoord 13

De stapel groeit naar beneden (op x86). De stapel wordt echter in één blok toegewezen wanneer de functie belast, en u hebt echter geen garantie in welke volgorde de artikelen op de stapel staan.

In dit geval heeft het ruimte toegewezen voor twee inten en een drie-int-array op de stapel. Het heeft ook na de array nog een extra 12 bytes toegewezen, dus het ziet er zo uit:

A [12 bytes]
opvulling (?) [12 bytes]
s [4 bytes]
q [4 bytes]

Om welke reden dan ook, heeft uw compiler besloten dat het 32 ​​bytes voor deze functie nodig had, en mogelijk meer. Dat is ondoorzichtig voor jou als een C-programmeur, je kunt niet weten waarom.

Als u wilt weten waarom, combineer ik de code op de montagetaal, ik ben van mening dat het -S -S op GCC en / S op MS’s C-compiler is. Als u naar de openingsinstructies aan die functie kijkt, ziet u de oude stapelaanwijzer die wordt opgeslagen en vervolgens 32 (of iets anders!) Afgetrokken. Vanaf daar kunt u zien hoe de code toegankelijk is dat 32-byte blok van geheugen en uitzoeken wat uw compiler aan het doen is. Aan het einde van de functie kunt u zien dat de stapelaanwijzer wordt hersteld.


Antwoord 14

Stack groeit naar beneden. SO F (G (H ())), de stapel die voor H wordt toegewezen, begint op het lagere adres, dan zullen G en G’s lager zijn dan F’s. Maar variabelen binnen de stapel moeten de C-specificatie volgen,

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

1206 Indien de objecten van hetzelfde geaggregeerde doel, de aanwijzers naar structuurleden die later worden gedeclareerd groter dan de aanwijzers die eerder in de structuur zijn gedeclareerd, te vergelijken, vergelijken de aanwijzers met grotere subscript-waarden met grotere subscripters van dezelfde array met lagere subscriptwaarden.

& amp; a [0] & lt; & AMP; A [1], moet altijd waar zijn, ongeacht hoe ‘A’ wordt toegewezen

Other episodes