Přetečení zásobníku

Aktuální verze stránky ještě nebyla zkontrolována zkušenými přispěvateli a může se výrazně lišit od verze recenzované 17. května 2021; kontroly vyžadují 4 úpravy .

V softwaru dochází k přetečení zásobníku , když je v zásobníku volání uloženo více informací, než může pojmout. Kapacita zásobníku se obvykle nastavuje na začátku programu/vlákna. Když ukazatel zásobníku překročí hranice, program se zhroutí. [jeden] 

K této chybě dochází ze tří důvodů. [2]

Nekonečná rekurze

Nejjednodušší příklad nekonečné rekurze v C :

int foo () { návrat foo (); }

Funkce bude volat sama sebe a zabírat místo v zásobníku, dokud zásobník nepřeteče a nedojde k chybě segmentace . [3]

Toto je rafinovaný příklad a ve skutečném kódu se nekonečná rekurze může objevit ze dvou důvodů:

Podmínka ukončení rekurze selhala

Častou příčinou nekonečné rekurze je situace, kdy za některých extrémních nevyzkoušených okolností podmínka ukončení rekurze nebude fungovat vůbec.

int faktoriál ( int n ) { if ( n == 0 ) návrat 1 ; návrat n * faktoriál ( n - 1 ); }

Pokud je n záporné , program přejde do hluboké (4 miliardy hovorů) rekurze .

Mnoho jazyků provádí optimalizaci zvanou „ tailová rekurze “. Rekurze na konci funkce se změní na smyčku a nespotřebovává zásobník [4] . Pokud taková optimalizace funguje, místo přetečení zásobníku bude smyčka .

Programátor napsal nepřímou rekurzi, aniž by si to uvědomoval

Programátor může napsat rekurzi neúmyslně, například když několik přetížených funkcí provádí stejnou funkci a jedna volá jinou.

int Obj::getData ( int index , bool & isChangeable ) { isChangeable = true ; return getData ( index ); } int Obj::getData ( int index ) { bool noMatter ; return getData ( index , noMatter ); }

Viz také Bludný kruh , Sepulki .

V rozhraních, jako jsou Qt a VCL , může k rekurzi dojít, pokud například obslužný program změny pole změní samotné pole.

Velmi hluboká rekurze

Jednotlivě propojený seznam můžete zničit pomocí následujícího kódu:

void zničitList ( struct Item * it ) { if ( it == NULL ) vrátit se ; zničitList ( it -> další ); zdarma ( to ); }

Tento algoritmus, pokud není seznam poškozen, teoreticky poběží v konečném čase a vyžaduje O( n ) zásobníku. Samozřejmě s dlouhým seznamem program selže. Možné řešení:

  • Najděte nerekurzivní algoritmus (funguje v tomto příkladu).
  • Přesuňte rekurzi z hardwarového zásobníku do dynamicky alokovaného (například při obcházení různých druhů sítí [5] ).
  • Pokud je rekurze hluboká, použijte jinou metodu. Například Quicksort je extrémně efektivní metoda třídění  , která může v extrémních případech využít spoustu místa na zásobníku. Proto implementace řazení v programovacích jazycích omezují hloubku rekurze, a pokud narazí na limit, používají pomalejší metody, jako je pyramidové třídění . Takto funguje například Introsort .

Velké proměnné na zásobníku

Třetím velkým důvodem přetečení zásobníku je jednorázová alokace obrovského množství paměti velkými lokálními proměnnými. Mnoho autorů doporučuje alokovat paměť větší než několik kilobajtů na " hromadě " spíše než na zásobníku. [6]

Příklad v C :

int foo () { double x [ 1000000 ]; }

Pole zabírá 8 megabajtů paměti; pokud na zásobníku není dostatek paměti, dojde k přetečení.

Cokoli, co snižuje efektivní velikost zásobníku, zvyšuje riziko přetečení. Vlákna obvykle zabírají méně zásobníku než hlavní program – program tedy může pracovat v režimu s jedním vláknem a selhat ve vícevláknovém režimu. Podprogramy běžící v režimu jádra často používají zásobník někoho jiného, ​​takže při programování v režimu jádra se snaží nepoužívat rekurzi a velké lokální proměnné. [7] [8]

Viz také

Poznámky

  1. Burley, James Craig Používání a portování GNU Fortran (odkaz není k dispozici) (1. června 1991). Získáno 30. března 2012. Archivováno z originálu 5. října 2012. 
  2. Danny, Kalev Understanding Stack Overflow (5. září 2000). Získáno 30. března 2012. Archivováno z originálu dne 27. září 2020.
  3. Jaký je rozdíl mezi poruchou segmentace a přetečením zásobníku? Archivováno 9. března 2012 na Wayback Machine na StackOverflow
  4. Úvod do schématu a jeho implementace (downlink) (19. února 1997). Získáno 30. března 2012. Archivováno z originálu 23. srpna 2000. 
  5. Příklad chyby Archivováno 13. listopadu 2020 na Wayback Machine v poly2tri , známé knihovně pro konstrukci omezené Delaunayovy triangulace polygonu; chyba byla opravena dynamickým zásobníkem STL .
  6. Feldman, Howard Modern Memory Management, část 2 (23. listopadu 2005). Získáno 30. března 2012. Archivováno z originálu 5. října 2012.
  7. Průvodce programováním jádra: Tipy pro výkon a stabilitu . Společnost Apple Inc. (7. listopadu 2006). Získáno 30. září 2017. Archivováno z originálu 7. prosince 2008.
  8. Dunlap, Randy Linux Kernel Development: Getting Started (downlink) (19. května 2005). Získáno 30. března 2012. Archivováno z originálu 5. října 2012.