2016-05-26 31 views
5

Jestem głównie zainteresowany żywotnością zmniejszanie takiej tablicy.Czy użycie realloc() na dynamicznie przydzielonej macierzy 2D jest dobrym pomysłem?

Pracuję nad projektem, w którym używałem pojedynczych wywołań malloc() do tworzenia pojedynczych średnich tablic 2D. (Co najwyżej kilkadziesiąt MiB, na największym.) Chodzi o to, że w ciągu życia jednej z tablic, jego zawartość dramatycznie się zmniejsza (o ponad połowę). Oczywiście mogłem po prostu zostawić rozmiar tablicy sam przez całe życie programu. (Jest to tylko x x MiB w systemie z dostępnym GiB RAM.) Ale, mówimy o ponad połowie przydzielonej przestrzeni, która zapada w pamięć, zanim program się zakończy i ze względu na naturę tego, w jaki sposób jestem przy użyciu tablicy wszystkie zachowane dane są przechowywane w ciągłym zestawie rzędów (na początku bloku). Wygląda na to, że marnowanie czasu na utrzymanie całej pamięci RAM, jeśli naprawdę tego nie potrzebuję.

Podczas gdy wiem, że realloc() może służyć do zmniejszania dynamicznie tworzonych tablic, tablica 2D jest bardziej złożona. Myślę, że rozumiem jego układ pamięci (jako że zaimplementowałem funkcję, która je strukturuje), ale to przesuwa granice mojej znajomości języka i działania jego kompilatorów. Oczywiście, musiałbym pracować z wierszami (i radzić sobie ze wskaźnikami wiersza), a nie tylko bajtami, ale nie wiem, jak przewidywalny byłby rezultat tego wszystkiego.

I tak, potrzebuję utworzyć tablicę z pojedynczym malloc(). Przedmiot ma kilka milionów wierszy. Próbowałem użyć pętli do malloc() każdego wiersza osobno, ale program zawsze zamarzał przy około 100 000 malloc() s.

Na tle źródło Używam skonstruować tablicę te przedstawia się następująco:

char ** alloc_2d_arr(int cnum, int rnum) { 
     /* ((bytes for row pointers + (bytes for data)) */ 
     char **mtx = malloc(rnum * sizeof (char *) + rnum * cnum * sizeof (char)); 

     /* Initialize each row pointer to the first cell of its row */ 
     char *p = (char *) (mtx + rnum); 
     for (int i = 0; i < rnum; i++) { 
       mtx[i] = p + i * cnum; 
     } 

     return mtx; 
} 
+2

'realloc' nie jest dobrym pomysłem na takie duże stoły, spójrz na drzewa" avl "i" czerwono-czarne ". –

+1

"Wygląda na to, że marnowanie czasu na utrzymanie całej pamięci RAM, jeśli naprawdę tego nie potrzebuję." - Pierwszy * profil *.Po drugie, realloc wiąże się z wysokim ryzykiem wywołania kopiowania wszystkich Twoich wewnętrznych danych na różne strony, których niebagatelne wydatki ponoszą tylko dlatego, że próbują oszczędzać pamięci RAM, ale nie stanowi to problemu. Jedynym scenariuszem wygrywającym tutaj jest 'realloc' zachowujący tę samą głowę regionu co twoja baza pamięci, a strony końcowe są zwracane do innego użytku; coś 'realloc' nie ma gwarancji na temat ... – WhozCraig

+0

... więc zamiast tego rozważałeś zrobienie 2 (lub 3 lub 4 dowolnych) alokacji, pamiętaj, które z nich ostatecznie przechowujesz, i' free() '- tych, których już nie potrzebujesz, gdy wydarzenie to się wydarzy? To znaczy. "zachowana" połowa twojej macierzy jest w pierwszej alokacji, druga połowa jest w innej alokacji, a ostatecznie po prostu uwolnisz drugą połowę. – WhozCraig

Odpowiedz

2

Korzystanie tablice wielowymiarowe, można to zrobić z lub bez wskaźniki do tablic o zmiennej długości. Ponieważ prawdopodobnie nie chcesz przydzielać dodatkowej pamięci, zostanie to zrobione na miejscu.

Pierwszy przeznaczyć 20 przez 10 tablicy:

int (*array)[10] = malloc(sizeof(int) * 20 * 10); 
for(size_t i = 0 ; i < 20 ; i++) 
    for(size_t j = 0 ; j < 10 ; j++) 
      array[i][j] = i * 100 + j; 

Jeśli chcesz zmienić liczbę wierszy, żadne elementy mają zostać przeniesione, jest potrzebny tylko realloc. Zmiana liczby wierszy na 15 jest banalna:

array = realloc(array , sizeof(int) * 15 * 10); 

Jeśli chcesz zmienić liczbę kolumn, elementy będą musiały zostać przeniesione. Ponieważ nie musimy kopiować pierwszej kolumny, kopiowanie rozpoczyna się od drugiej. Funkcja memmove służy do uniknięcia nakładania się pamięci, co w tym przypadku nie może się zdarzyć, ale może się zdarzyć, jeśli nowa kolumna będzie większa. Pozwala również uniknąć problemów z aliasingiem. Zauważ, że ten kod jest zdefiniowany tylko dlatego, że używamy przydzielonej pamięci. Zmieńmy liczyć Kolumna do 3:

int (*newarray)[3] = (int(*)[3])array; 
for(size_t j = 1 ; j < 15 ; j++) 
    memmove(newarray[j] , array[j] , sizeof(int) * 3); 
newarray = realloc(array , sizeof(int) * 15 * 3); 

przykład robocza: https://ideone.com/JMdJO0

Jeżeli nowa liczba kolumn bywa większy niż stary, a następnie pamięć musi najpierw zostać przesunięte (po prostu dostać więcej miejsca), a następnie rozpocznie się kopiowanie kolumn, zaczynając od ostatniej kolumny.

+0

Przykro mi to przyznać, ale mam problem ze zrozumieniem 'int (* array) [10] = malloc (...);'. Opierając się na moim pozornie niedostatecznym zrozumieniu C, wygląda na to, że zainicjowałem nowo utworzoną zmienną ze wskaźnikiem odwołanym jako jego identyfikatorem. Jedną rzeczą byłoby wyodrębnienie potrójnego wskaźnika (dwa razy) i nadanie temu wynikowi funkcji malloc(), ale umieszczenie typu z przodu sprawia, że ​​wygląda jak adres RAM jest używany jako symbol (co nie ma sensu) . Czuję, że widzę cokolwiek to jest, gdy uczyłem się C, ale wyszukiwanie odpowiednich terminów oczywiście daje zanieczyszczone wyniki. – CircleSquared

+0

@CircleSquared Jest to wskaźnik do tablicy wielowymiarowej. Dwa wymiary w moim przykładzie. W ten sposób: 'int a [7] [9]; int (* pa) [9] = a; ', z wyjątkiem mojego przykładu nie wskazuję tego wskaźnika na automatyczną tablicę, ale na przydzieloną pamięć. – 2501

+0

Dziękuję za pokazanie mi bardziej efektywnego sposobu dynamicznego przydzielania wielowymiarowej tablicy ** dodatkowo ** po prostu odpowiadając na moje pytanie. * Więc jest wystarczająco bezpieczny, ale jest to lepszy sposób na ustawienie tablicy. * – CircleSquared