Permalänk

Stack och heap i C/C++

Någon som kan ge några tumregler för vilket man ska välja?
Stack verkar fungera för måttligt stora program.
Vid någon gräns fungerar enbart heap....eller?

Permalänk
Medlem

Taget från en annan tråd då jag inte orkar skriva.

Skrivet av tufflax:

Ok, jag skrev om hur det fungerar på svenska. Men ett extremt hett tips: Lär dig engelska! Det är extremt viktigt, inte bara för programmering. Det är typ det viktigaste du kan göra, tätt efter att äta mat och andas.

En stack (stack = stapel på svenska) är en datastruktur där man bara kan lägga
till nya element överst, och ta bort element överst, d.v.s. i omvänd ordning som
man la till dem (som på en stapel tallrikar). Stacken, i bestämd form, är en
sådan datastruktur som programmet använder när det körs för att lagra data som
det (programmet) behöver.

På stacken läggs lokala variabler och argument. Säg att du har följande kod.
(Koden är ganska meningslös, men jag hade dålig fantasi.)

int add(int x, int y) { int result = x + y; return result; } void print_times(int x, int times) { for (int i = 0; i < times; i++) { Console.WriteLine(x); } } void add_and_print_3_times(int a, int b) { int sum = add(a, b); print_times(sum, 3); } static int Main(string[] args) { add_and_print_3_times(3, 17); return 0; }

Du är i början av main. Då ser stacken ut som följer:

-----------------------------
args: 0x239797
-----------------------------

D.v.s. det finns bara en sak på stacken: variablen args som är en referens
till en string[].

När vi anropar add_and_print_3_times(3, 17) så kommer det minne som den
metoden behöver att allokeras på stacken, så i början av
add_and_print_3_times(3, 17) så ser stacken ut:

-----------------------------
a: 3
b: 17
sum: (inget)
-----------------------------
args: 0x239797
-----------------------------

Strecken här (---) använder jag för att man ska se var metoders "stack frames"
börjar och slutar. Det första add_and_print_3_times gör är att anropa
add(3, 17). Då ser stacken ut:

-----------------------------
x: 3
y: 17
result: (inget)
-----------------------------
a: 3
b: 17
sum: (inget)
-----------------------------
args: 0x239797
-----------------------------

Nästa sak programmet gör är att köra raden `int result = x + y;`. Programmet
lagrar då 20 i resultat-platsen på stacken:

-----------------------------
x: 3
y: 17
result: 20
-----------------------------
a: 3
b: 17
sum: (inget)
-----------------------------
args: 0x239797
-----------------------------

När add-metoden returnerar så tas det minnet bort från stacken (det ligger
överst, eller hur?). Säg att tilldelningen i raden `int sum = add(a, b);`
också sker, då kommer stacken att se ut:

-----------------------------
a: 3
b: 17
sum: 20
-----------------------------
args: 0x239797
-----------------------------

Nu ska print_times(20, 3) anropas. print_times har också lokala variabler och
argument, så stacken kommer att se ut:

-----------------------------
x: 20
times: 3
i: (inget)
-----------------------------
a: 3
b: 17
sum: 20
-----------------------------
args: 0x239797
-----------------------------

Medan loopen i print_times körs så kommer givetvis `i` att uppdateras. När
print_times är klar så tas det minnet bort igen, och vi får kvar:

-----------------------------
a: 3
b: 17
sum: 20
-----------------------------
args: 0x239797
-----------------------------

Nu är add_and_print_3_times också klar, och vi får kvar:

-----------------------------
args: 0x239797
-----------------------------

Sammanfattningvis: På stacken läggs lokala variabler och argument (och kanske
lite mer grejer, men det är inte jätteviktigt). Det är viktigt att inse att
när man anropar en metod så kommer alltid metodens stack frame (dess variabler
och argument) att hamna överst på stacken. Och när man är klar med en metod så
kommer alltid dess stack frame att ligga överst, så vi kan ta bort den utan
problem.

Så hur används heapen?

Jo, args i Main är ju en string[]. string[]:er läggs på heapen, och det som
sparas på stacken, (här: `args: 0x239797`) är en adress till den. Så på
heapen, på plats 0x239797 så ligger en string[]. Så är det med alla lokala
variabler som är av en referenstyp, d.v.s. inte en primitiv (int, double,
bool, o.s.v.). Om en stackframe som refererar till ett objekt tas bort (för
att vi är klara med den metoden) så förlorar vi referensen till objektet om vi
inte hade sparat undan den nånstans. Om vi nu inte har kvar några referenser
till objektet så kommer objektet så småningom att städas undan av garbage
collectorn.

Notera att 0x239797 är ett vanligt nummer, men man brukar skriva adresser i hexadecimalform (0x betyder hexadecimalform).

Permalänk

@Petrikus: Tack för svaret!

Jag tror detta visar när jag har problem:

#include <iostream>
int main()
{
int stack [1000000]; // 4 Mbytes big
std::cout<<" stack-test ";
return 0;
}

Detta slutar fungera om jag ökar stacken med en nolla till.
Trist då att kompilatorn inte ger någon varning....

Men om man kör malloc så kan man allokera betydligt mer.
Det ser ut att vara vanligaste sättet för stora arrayer med double eller float.
Men man måste tydligen frigöra minne på slutet.

Permalänk
Medlem

Hur stor stack du kan använda beror på vilken miljö som koder körs under.
I många fall är maximal stack storlek begränsad endast av tillgängligt minne, men i andra fall kan den vara ganska så liten. För kod inuti Linux-kerneln så är stacken t.ex. begränsad till två minnessidor - 8KB/16/KB för 32-/64-bitars arkitekturer.

Allokeringar på heapen har inga särskilda begränsningar annat än de begränsningar som finns på programmet som helhet.

Sen så är det ju stor skillnad på hur minnet hanteras när det gäller stack kontra heap. Stackalloceringar är lokala för ett givet funktionsanrop och försvinner därmed när funktionen är klar.
Allokeringar på heapen måste man ofta frigöra själv när man är klar med dem, såvida man inte använder ett programmeringsspråk med automatisk garbage collection.

.

Permalänk

Det ser ut att vara stor skillnad på Linux för X86_64 och Raspberry Pi4(ARM64).
I bägge fall verkar "default stack" vara 8192 bytes. Men X86_64 klarar sig betydligt längre innan man får fel.
Kan det bero på att swap är betydligt större?(Lika stor som RAM)

Med Raspberry Pi får man köra ulimit -s unlimited så fungerar det mesta.