Pytanie Czy mogę założyć, że wywołanie realloc przy mniejszym rozmiarze spowoduje uwolnienie pozostałej części?


Rozważmy ten bardzo krótki fragment kodu:

#include <stdlib.h>

int main()
{
    char* a = malloc(20000);
    char* b = realloc(a, 5);

    free(b);
    return 0;
}

Po przeczytaniu strony podręcznika dla realloc, nie byłem do końca pewien, czy druga linia spowoduje uwolnienie dodatkowych bajtów w 19995. Aby zacytować stronę man: The realloc() function changes the size of the memory block pointed to by ptr to size bytes., ale czy z tej definicji mogę być pewny, że reszta zostanie uwolniona?

Mam na myśli blok wskazany przez b na pewno zawiera 5 wolnych bajtów, więc czy wystarczyłoby leniwemu przydzielającemu się alokatorowi, żeby po prostu nic nie robił dla linii realloc?

Uwaga: Alokator, którego używam, wydaje się uwalniać 19 995 dodatkowych bajtów, jak to pokazuje valgrind podczas komentowania free(b) linia :

==4457== HEAP SUMMARY:
==4457==     in use at exit: 5 bytes in 1 blocks
==4457==   total heap usage: 2 allocs, 1 frees, 20,005 bytes allocated

21
2018-03-05 22:36


pochodzenie


Nagłówek <malloc.h> nie jest zdefiniowany przez Standard: wolą używać <stdlib.h>. Również przesyłanie wartości zwracanej z malloc (lub realloc) nie służy żadnemu przydatnemu celowi i może ukrywać błąd (Reprezentacja void* i int być innym) kompilator mógłby złapać inaczej. - pmg
"Rozmiar bloku pamięci wskazywanego przez parametr ptr jest zmieniany na rozmiar bajtów, zwiększając lub zmniejszając ilość pamięci dostępnej w bloku." - Matt Phillips
@pmg ok Nie wiedziałem tego. Zmienię mój fragment - qdii
Zauważ, że Valgrind nie odzwierciedla wydajności twojego normalnego programu przydzielania, ponieważ w rzeczywistości zastępuje on zwykle używany alokator, z którego korzysta Valgrind. - Gamrix


Odpowiedzi:


Tak, gwarantowane przez standard C, jeśli nowy obiekt może zostać przydzielony.

(C99, 7.20.3.4p2) "Funkcja realloc deallocates stary obiekt wskazywany przez ptr i zwraca wskaźnik do nowego obiektu, który ma rozmiar określony przez rozmiar."


21
2018-03-05 22:55



Projekt C99 nigdy nie wspomina nic o realloc nie robiąc nic, gdy stary rozmiar jest równy nowemu. Zgaduję, że wszystkie implementacje go naruszają? - qdii
C99 7.20.3.4 §4: Funkcja realloc zwraca wskaźnik do nowego obiektu (który może mieć taką samą wartość jak wskaźnik do starego obiektu) - Christoph


Tak, jeśli się uda.

Twój fragment kodu pokazuje znany, nikczemny błąd:

char* b = (char*) realloc(a, 5);

Jeśli się to uda, pamięć poprzednio przydzielona a zostaną uwolnieni, i b wskaże 5 bajtów pamięci, które mogą, ale nie muszą pokrywać się z oryginalnym blokiem.

jednak, jeśli połączenie nie powiedzie się, b będzienull, ale a nadal będzie wskazywać na swoją pierwotną pamięć, która będzie nadal ważna. W takim przypadku musisz free(a) w celu zwolnienia pamięci.

Jest jeszcze gorzej, jeśli używasz wspólnego (niebezpiecznego) idiomu:

a = realloc(a, NEW_SIZE);     // Don't do this!

Jeśli zadzwonisz do realloc zawodzi, a będzie null a jego pierwotna pamięć zostanie osierocona, co spowoduje jej bezpowrotną utratę do czasu zakończenia programu.


15
2018-03-05 22:51



Masz rację. Widziałem, jak cppcheck narzeka na to. Domyślam się, że ten jest zabójcą do testów egzaminacyjnych, ale czy kiedykolwiek to się zdarzyło? - qdii
@qdii: w osadzonym świecie wszystkie zakłady są wyłączone; również os może wymuszać pewne ograniczenia, np. w przestrzeni pamięci wirtualnej za pośrednictwem ulimit - Christoph
Tak, to naprawdę dzieje się w prawdziwym świecie. Dlaczego wydaje ci się, że tyle nieudanego oprogramowania ulega awarii, kiedy zabraknie ci pamięci (w systemie bez wymiany), zamiast dawać ci wiadomość, że nie może wykonać żądanej operacji z powodu braku pamięci? - R..


To zależy od twojej implementacji libc. Wszystkie poniższe zachowania są zgodne:

  • nie robienie niczego, tj. pozostawienie danych w miejscu, w którym się znajduje i zwrócenie starego bloku, ewentualnie ponowne wykorzystanie nieużywanych bajtów do dalszych przydziałów (afaik takie ponowne użycie nie jest powszechne)
  • kopiowanie danych do nowego bloku i zwalnianie starego z powrotem do systemu operacyjnego
  • kopiowanie danych do nowego bloku i zachowywanie starego do dalszych przydziałów

Możliwe jest również

  • zwraca wskaźnik zerowy, jeśli nie można przydzielić nowego bloku

W tym przypadku stare dane również pozostaną tam, gdzie są, co może prowadzić do wycieków pamięci, np. Jeśli zwracana jest wartość realloc() zastępuje jedyną kopię wskaźnika do tego bloku.

Rozsądna implementacja libc użyje heurystyki do określenia, które rozwiązanie jest najbardziej wydajne.

Należy również pamiętać, że opis ten znajduje się na poziomie wdrożenia: semantycznie, realloc() zawsze zwalnia obiekt, dopóki alokacja nie zakończy się niepowodzeniem.


2
2018-03-05 22:51



Jeśli chodzi o "Afaik takie ponowne użycie nie jest powszechne", nigdy nie słyszałem o implementacji w świecie rzeczywistym, która pozostawia starą alokację w miejscu i nie uwalnia ogona do ponownego użycia. Byłoby to patologicznie złe, choć legalne, zachowanie. - R..
Czy istnieją jakieś standardy, które definiują to zachowanie, czy naprawdę zależy to od implementacji libc? - rr-
@ rr-: standard C oraz POSIX pozostawiają to do wdrożenia; Nie jestem świadomy żadnych specyfikacji, które definiują zachowanie, ale może to być udokumentowane w podręczniku libc - Christoph


Funkcja realloc ma następującą umowę:

void * result = realloc (ptr, new_size)

  • Jeśli wynik jest równy NULL, to ptr jest nadal ważny i niezmieniony.
  • Jeśli wynik nie jest równy NULL, to ptr jest teraz nieważny (tak jakby został zwolniony) i nie wolno go używać ponownie. wynik jest teraz wskaźnikiem do dokładnie nowego rozmiaru bajtów danych.

Dokładne szczegóły tego, co dzieje się pod maską, są specyficzne dla implementacji - na przykład wynik może być równy ptr (ale dodatkowa przestrzeń poza new_size nie może być już dotykana), a realloc może wywoływać za darmo, lub może wykonywać własną wewnętrzną swobodną reprezentację. Kluczową sprawą jest to, że jako programista nie ponosisz już odpowiedzialności za ptr, jeśli realloc zwróci wartość inną niż null, a ty nadal będziesz za to odpowiedzialny, jeśli realloc zwróci wartość NULL.


1
2018-03-05 23:12



Ponadto przekazanie new_size = 0 jest takie samo jak bezpłatne (ptr) i zwraca NULL. To byłby przypadek, w którym wynik == NULL i ptr nie są już ważne. - gnasher729


Wydaje się mało prawdopodobne, że uwolniono 1999 bajtów. Bardziej prawdopodobne jest to, że realloc zastąpił blok 20000-bajtowy innym blokiem 5-bajtowym, oznacza to, że blok 20000-bajtowy został zwolniony i przydzielono nowy blok 5-bajtowy.


0
2018-03-05 22:54