Na forum 4programmers.net korzystamy z plików cookies. Część z nich jest niezbędna do funkcjonowania
naszego forum, natomiast wykorzystanie pozostałych zależy od Twojej dobrowolnej zgody, którą możesz
wyrazić poniżej. Klikając „Zaakceptuj Wszystkie” zgadzasz się na wykorzystywanie przez nas plików cookies
analitycznych oraz reklamowych, jeżeli nie chcesz udzielić nam swojej zgody kliknij „Tylko niezbędne”.
Możesz także wyrazić swoją zgodę odrębnie dla plików cookies analitycznych lub reklamowych. W tym celu
ustaw odpowiednio pola wyboru i kliknij „Zaakceptuj Zaznaczone”. Więcej informacji o technologii cookie
znajduje się w naszej polityce prywatności.
Cześć!
Co zadeklarowane przeze mnie ląduje na stosie a co nie?
#include <bits/stdc++.h>
using namespace std;
const int MILION = 1e6;
void rec(int i){
if (i == 0)
return;
int niecenzuralne_slowo = 420 * 2137;
return rec(i-1);
}
int main(){
int n[MILION * 3]; // powoduje przepelnienie
rec(MILION); // powoduje przepelnienie
int a[MILION], b[MILION], c[MILION]; // nie powoduje przepelnienia
vector<int> vec(MILION * 69); // nie powoduje przepelnienia
int m;
return 0;
}
Ten stos jest w trakcie programu zwalniany? Mógłby ktoś napisać coś o tym, albo podesłać jakiś link bo nie rozumiem jak to działa.
0
Najprościej skompiluj z opcją produkcji asemblera i sam zobacz.
8
C++ jako standard nie definiuje tego.
To czy i w jakich okolicznościach następuje przepełnienie stosu, decyduje: platforma, kompilator i jego ustawienia.
Przykładowo twoja funkcja rec nic nie robi. Sprytny kompilator, z właściwymi ustawieniami optymalizacji po prostu usunie tą funkcję, albo zamieni rekursję na zwykłą pętlę i przepełnienia stosu nie będzie.
Tak samo kompilator widząc, że te twoje tablice nie są używane może je zignorować i znowu przepełnienia stosu nie będzie.
Przykład tutaj kompilator zostawił tylko std::vector resztę zignorował. rec wygenerował jako puste a i tak go nie używa.
A tutaj po dodaniu czegoś, by rec cokolwiek robiło, rekursja została zamieniona na pętlę i znowu przepełnienia stosu nie będzie.
0
A co gdy program np. zużywa 100MB pamięci, na pewno przepełniłby stos ale jednak nie powoduje błędu. Gdzie ta pamięc jest przechowywana?
2
Po pierwsze nie na pewno no bo rozmiar stosu sobie mozesz dostosowac a po drugie masz jeszcze sterte.
5
@Suchy702:
masz mniej więcej 3 typy przestrzeni na zmienne
1 - na stosie
2 - na heapie
3 - zmienne globalne
Kiedy jest alokacja na stosie? Jak robisz zmienne automatyczne, tzn np.
int main() {
int tab[200];
}
wtedy, (zakładając, że nie będzie to wyoptymalizowane), tablica tab wyląduje na stosie, i zajmie sizeof(int)*200 bajtów.
Zazwyczaj też, aby umożliwić return w funkcjach, kompilator przy wejściu do funkcji odkłada na stos adres powrotu. Efektywnie to jest coś w stylu
void g() {
// cośtam
void* x;
void** adres_powrotu = &x - 1;
goto *adres_powrotu;
}
void f() {
void* adres_powrotu = &ret_here; // wiem, że to składniowo nie jest poprawne
g();
ret_here:
}
wtedy nieskończona rekurecja (czy też odpowiednio głęboka po prostu), może spowodować (słynne) stack overflow - każde wywołanie zwiększa wielkość stosu o ileś bajtów - dlatego też rozmiar stosu jest tak rygorystycznie ograniczany - bardzo prosto by było zrobić błędnie działające programy które przypadkiem są nieefektywne.
Alokacja na heapie:
wszystko co używa malloc/new, np. new int[2137], ale też np. wektor, czy wszystkie chyba kontenery STL z wyjątkiem std::array. Możesz się na tym poznać, patrząc, że na typowej implementacji, sizeof(vector) == 24. Jest to rozmiar stały, niezależnie od ilości elementów. Implementowane jest to tak, że siedzi tam w środku wskaźnik na tablicę zaalokowaną za pomocą new.
Zmienne globalne - one mają jeszcze osobne miejsce w pamięci - informacja o zmiennych globalnych jest zawarta bezpośrednio w pliku wykonywalnym (a jeśli np. jest to nieinicjalizowana zmienna globalna, to szczęśliwie jest tam umieszczana tylko informacja o tym jak dużo tych zer trzeba umieścić). Takie zmienne nie mogą rosnąć w sposób niekontrolowany, więc też ograniczenia na ich rozmiar są znacznie bardziej zrelaksowane.
Trochę szczegółowiej mówi o tym Gynvael na jednym ze streamów, o tutaj:
0
"Jak działa przepełnienie stosu w C++?"
obok kompetentnych odpowiedzi Kolegów, podkreślę mocno jedno:
@Suchy702
Odmiennie niż w Javie, Pythonie, C# (wymień jeszcze kilka języków - natychmiast leci wyrazisty wyjątek z opisem co, jak i z jakiego miejsca), to naruszenia zasad, granic w C/C++ przejawiają się odmiennie:
nie ma jakiegoś jednego modelu zachowania. Tu wszystko jest UB Undefined Behaviour, czyli np pewne wystąpienia błędów mogą nie dawać widocznych przejawów, mogą dawać efek z opóźnieniem nawet minut (gdy ulegają zniszczeniu jakieś struktury systemowe), uszkadzać jakieś dane, problem być może się ujawni przy ich użyciu.
Podobieństwa mogą mieć języki un-safe generujące kod maszynowy, Delphi (choć tam np granice tablic mogą być bronione przez kompilator)
Oczywiście, w rękach doświadczonego programisty mgiełka UB nabiera częściowej powtarzalności, jak się widziało jedno, drugie, trzecie przejechanie granic
0
Widzę @Suchy702 że temat pytania jest odmienny od treści, bo w teści pytasz już nie przepełnienie, ale o realia zmiennych różnych -> modeli pamięci
void rec(int i){
if (i == 0)
return;
int niecenzuralne_slowo = 420 * 2137;
return rec(i-1);
Zazwyczaj każde wywołanie funkcji odkłada na stos adres powrotu (czyli wskaźnik na kod zaraz za wywołaniem funkcji) dzięki czemu procesor wie gdzie wrócić w momencie wyjścia z funkcji. Szczegóły zależą od platformy, kompilatora, jego ustawień, ale generalnie musimy pamiętać że zbyt głębokie wywołanie (rekurencyjne bądź nie) może spowodować przepełnienie stosu.
int main(){
int n[MILION * 3]; // powoduje przepelnienie
To idzie na stos.
rec(MILION); // powoduje przepelnienie
Z powodu o którym napisałem wyżej - odkładasz na stos milion razy adres powrotu.
int a[MILION], b[MILION], c[MILION]; // nie powoduje przepelnienia
To też idzie na stos.
vector<int> vec(MILION * 69); // nie powoduje przepelnienia
Tu na stos idzie tylko niewielki obiekt typu vector<int>. Same dane są przechowywane na stercie.
int m;
To też idzie na stos (teoretycznie; kompilator taką zmienną zapewne wyoptymalizuje).
Ten stos jest w trakcie programu zwalniany? Mógłby ktoś napisać coś o tym, albo podesłać jakiś link bo nie rozumiem jak to działa.
Wyjście zmiennej z zakresu, czyli wyjście z bloku { } w którym była zmienna zadeklarowana zwalnia fragment stosu zaalokowany w tym bloku.
1 użytkowników online, w tym zalogowanych: 0, gości: 1