Błąd segmentacji w przypadku dużych rozmiarów macierzy

116

Poniższy kod powoduje błąd segmentacji podczas uruchamiania na maszynie 2Gb, ale działa na maszynie 4GB.

int main()
{
   int c[1000000];
   cout << "done\n";
   return 0;
}

Rozmiar tablicy to zaledwie 4 MB. Czy istnieje ograniczenie rozmiaru tablicy, której można używać w języku C ++?

Mayank
źródło

Odpowiedzi:

130

Prawdopodobnie po prostu pojawia się przepełnienie stosu. Tablica jest zbyt duża, aby zmieścić się w przestrzeni adresowej stosu programu.

Jeśli alokujesz tablicę na stercie, wszystko powinno być w porządku, zakładając, że twoja maszyna ma wystarczającą ilość pamięci.

int* array = new int[1000000];

Ale pamiętaj, że będzie to wymagało delete[]od tablicy. Lepszym rozwiązaniem byłoby użycie std::vector<int>i zmiana rozmiaru na 1000000 elementów.

Charles Salvia
źródło
3
Dzięki za odpowiedź, ale czy mógłbyś mi wyjaśnić, dlaczego tablice są przydzielane na stosie i dlaczego nie w głównej pamięci programu.
Mayank
18
Podany kod jest przydzielany na stosie, ponieważ jest określony jako tablica ze stałą liczbą elementów w czasie kompilacji. Wartości są umieszczane na stosie tylko z malloc, nowym itp.
Seth Johnson
6
Wszystkie automatyczne zmienne są przydzielane na stosie. Jeśli spojrzysz na disasseble, zobaczysz rozmiar swoich zmiennych lokalnych odjęty od wskaźnika stosu. Kiedy wywołujesz malloc, calloc lub jakąkolwiek funkcję pamięci, funkcje idą i znajdują bloki pamięci wystarczająco duże, aby zaspokoić twoje wymagania.
powtórz
@Charles, dlaczego moglibyśmy przydzielić więcej pamięci ze stosu, a nie ze stosu? z mojego zrozumienia, zarówno stos, jak i sterta poruszają się w przeciwnym kierunku w przydzielonej przestrzeni adresowej w pamięci.
saurabh agarwal
2
@saurabhagarwal Sterta się nie porusza. Nie jest to nawet ciągły region pamięci. Alokator po prostu zwraca wolny blok pamięci, który odpowiada wymaganiom dotyczącym rozmiaru. Co i gdzie jest stos i sterta?
phuclv
56

W C lub C ++ obiekty lokalne są zwykle przydzielane na stosie. Alokujesz dużą tablicę na stosie, więcej niż może obsłużyć stos, więc otrzymujesz przepełnienie stosu .

Nie przydzielaj go lokalnie na stosie, zamiast tego użyj innego miejsca. Można to osiągnąć poprzez uczynienie obiektu globalnym lub przydzielenie go na globalnej stercie . Zmienne globalne są w porządku, jeśli nie używasz żadnej innej jednostki kompilacji. Aby upewnić się, że nie stanie się to przypadkowo, dodaj statyczny specyfikator magazynu, w przeciwnym razie po prostu użyj sterty.

Spowoduje to alokację w segmencie BSS, który jest częścią sterty:

static int c[1000000];
int main()
{
   cout << "done\n";
   return 0;
}

Spowoduje to alokację w segmencie DATA, który jest również częścią sterty:

int c[1000000] = {};
int main()
{
   cout << "done\n";
   return 0;
}

Spowoduje to przydzielenie w jakiejś nieokreślonej lokalizacji w stercie:

int main()
{
   int* c = new int[1000000];
   cout << "done\n";
   return 0;
}
Gunther Piez
źródło
Jeśli używasz trzeciego wzorca, alokując na stercie, nie zapomnij usunąć [] wskaźnika na jakimś etapie, bo inaczej wycieknie pamięć. Lub spójrz na inteligentne wskazówki.
davidA
8
@meowsqueak Oczywiście jest to dobra praktyka deletewszędzie tam, gdzie się znajdujesz new. Ale jeśli jesteś pewien, że przydzielasz pamięć tylko raz (jak w main), nie jest to absolutnie potrzebne - pamięć jest na pewno zwolniona przy wyjściu z maina nawet bez jawności delete.
Gunther Piez,
'at'drhirsch (jak w ogóle robisz postać ata?) - tak, uczciwy komentarz. Ponieważ OP wydaje się nowy w języku, chciałem się tylko upewnić, że oni i wszyscy inni widzący twoją dobrą odpowiedź byli świadomi konsekwencji trzeciej opcji, jeśli jest używana ogólnie.
davidA
15

Ponadto, jeśli pracujesz w większości systemów UNIX i Linux, możesz tymczasowo zwiększyć rozmiar stosu za pomocą następującego polecenia:

ulimit -s unlimited

Ale uważaj, pamięć to ograniczony zasób, a z wielką mocą wiąże się wielka odpowiedzialność :)

RSFalcon7
źródło
1
To jest rozwiązanie, ale radzę wszystkim zachować szczególną ostrożność podczas usuwania domyślnych ograniczeń rozmiaru stosu programu. Doświadczysz nie tylko poważnego spadku wydajności, ale system może się zawiesić. Na przykład próbowałem posortować tablicę zawierającą 16 000 000 elementów całkowitych za pomocą quicksort na komputerze z 4 GB pamięci RAM i mój system był prawie zabity. LOL
rbaleksandar
@rbaleksandar Myślę, że ~ 16MB program prawie zabił twoją maszynę, ponieważ pracowałeś z kilkoma kopiami tablicy (może być jedna na wywołanie funkcji?) Wypróbuj implementację bardziej świadomą pamięci;)
RSFalcon7
Jestem prawie pewien, że obsługa tablicy jest w porządku, ponieważ przekazuję przez odwołanie, a nie przez wartość. To samo dzieje się z bąbelkami. Do diabła, nawet jeśli moja implementacja quicksort jest do bani, bubblesort jest czymś, czego nie możesz nieprawidłowo zaimplementować. LOL
rbaleksandar
LOL, możesz spróbować radix sort lub po prostu użyć std :: sort :)
RSFalcon7
1
Bez szans. To zadanie laboratoryjne. : D
rbaleksandar
3

Twoja tablica jest alokowana na stosie, w tym przypadku spróbuj zaalokować tablicę o tym samym rozmiarze za pomocą funkcji alert.

ponownie odtwarzać
źródło
3

Ponieważ przechowujesz tablicę na stosie. Powinieneś go przechowywać na stercie. Zobacz ten link, aby zrozumieć koncepcję sterty i stosu.

Narek
źródło