Jestem trochę zdezorientowany, jak sprawdzić, czy alokacja pamięci nie powiodła się, aby zapobiec niezdefiniowanym zachowaniom spowodowanym przez dewelowaną {X0}} wskaźnik. Wiem, że malloc
(i podobne funkcje) może zawieść i zwrócić NULL
, a z tego powodu adres zwrócony powinien być zawsze sprawdzany przed kontynuowaniem z resztą programu. To, czego nie dostaję, to najlepszy sposób na radzenie sobie z tymi przypadkami. Innymi słowy: Jaki jest program, który ma robić, gdy malloc
Powraca NULL
?
Pracowałem nad tym wdrożeniem podwójnie połączonej listy, gdy ta wątpliwość podniesiona.
struct ListNode {
struct ListNode* previous;
struct ListNode* next;
void* object;
};
struct ListNode* newListNode(void* object) {
struct ListNode* self = malloc(sizeof(*self));
if(self != NULL) {
self->next = NULL;
self->previous = NULL;
self->object = object;
}
return self;
}
Inicjalizacja węzła występuje tylko wtedy, gdy jego wskaźnik został prawidłowo przydzielony. Jeśli tak się nie stało, ta funkcja konstruktora zwraca NULL
.
Napisałem również funkcję, która tworzy nowy węzeł (wywołanie funkcji newListNode
) począwszy od już istniejącego węzła, a następnie powraca.
struct ListNode* createNextNode(struct ListNode* self, void* object) {
struct ListNode* newNext = newListNode(object);
if(newNext != NULL) {
newNext->previous = self;
struct ListNode* oldNext = self->next;
self->next = newNext;
if(oldNext != NULL) {
newNext->next = oldNext;
oldNext->previous = self->next;
}
}
return newNext;
}
Jeśli newListNode
zwraca NULL
, createNextNode
, createNextNode
, a także zwroty NULL
i węzeł przekazywany do funkcji nie są dotknięte.
Następnie struktura Listnode jest używana do wdrożenia rzeczywistej listy połączonej.
struct LinkedList {
struct ListNode* first;
struct ListNode* last;
unsigned int length;
};
_Bool addToLinkedList(struct LinkedList* self, void* object) {
struct ListNode* newNode;
if(self->length == 0) {
newNode = newListNode(object);
self->first = newNode;
}
else {
newNode = createNextNode(self->last, object);
}
if(newNode != NULL) {
self->last = newNode;
self->length++;
}
return newNode != NULL;
}
Jeśli utworzenie nowego węzła nie powiedzie się, funkcja addToLinkedList
zwraca 0, a sama lista połączona jest nietknięta.
Wreszcie, rozważmy tę ostatnią funkcję, która dodaje wszystkie elementy połączonej listy na inną linkę.
void addAllToLinkedList(struct LinkedList* self, const struct LinkedList* other) {
struct ListNode* node = other->first;
while(node != NULL) {
addToLinkedList(self, node->object);
node = node->next;
}
}
Jak powinienem poradzić sobie z możliwością zwrotu addToLinkedList
? Za to, co zebrałem, malloc
nie powiedzie się, gdy nie jest już możliwe przydzielenie pamięci, więc zakładam, że kolejne połączenia po awarii alokacji również nie mam racji? Więc jeśli zostanie zwrócone 0, jeśli pętla natychmiast się zatrzyma, ponieważ w każdym razie nie będzie można dodać żadnych nowych elementów do listy? Również jest poprawny, aby stosować wszystkie te sprawdzanie jednego nad innym sposobem, w jaki to zrobiłem? Czy nie jest zbędny? Czy błędnie byłoby natychmiast zakończyć program, gdy tylko Malloc się nie powiedzie? Czytałem, że byłoby problematyczne dla programów wielokrotnych, a także, że w niektórych istnieje program może być w stanie kontynuować uruchomienie bez dalszej alokacji pamięci, więc błędnie byłoby traktować to jako błąd fatalny w jakimkolwiek możliwym przypadku . Czy to jest poprawne?
Przepraszamy za naprawdę długiego postu i dziękuję za pomoc!
2 odpowiedzi
Jak radzić sobie z brakiem pracy Malloc i zwracając NULL?
Zastanów się, czy kod jest zestawem funkcji / biblioteki / biblioteki lub aplikacji pomocniczych.
Decyzja o zakończeniu jest najlepiej obsługiwana przez kod wyższego poziomu.
Przykład: oprócz exit()
, abort()
i znajomych, standardowa biblioteka C nie wychodzi.
Podobnie powracające kody / wartości błędów jest rozsądnym rozwiązaniem dla zestawów funkcji niskiego poziomu OP. Nawet dla addAllToLinkedList()
, rozważę propagowanie błędu w kodzie powrotnym. (Nie zero jest błędem.)
// void addAllToLinkedList(struct LinkedList* self, const struct LinkedList* other) {
int addAllToLinkedList(struct LinkedList* self, const struct LinkedList* other) {
...
if (addToLinkedList(self, node->object) == NULL) {
// Do some house-keepeing (undo prior allocations)
return -1;
}
W przypadku aplikacji wyższego poziomu podążaj za projektem. Na razie może być wystarczająco proste, aby wyjść z komunikatu niepowodzenia.
if (addAllToLinkedList(self, ptrs)) {
fprintf(stderr, "Linked List failure in %s %u\n", __func__, __LINE__);
exit(EXIT_FAILURE);
}
Przykład nie wychodzenia:
Rozważ procedurę, która odczytuje plik do struktury danych z wieloma zastosowaniem LinkedList
, a plik był w jakiś sposób uszkodzony prowadzący do nadmiernych alokacji pamięci. Kod może chcesz po prostu uwolnić wszystko dla tego pliku (ale tylko dla tego pliku), i wystarczy zgłosić się do użytkownika "Nieprawidłowy plik / out-of-pamięci" - i kontynuuj uruchomienie.
if (addAllToLinkedList(self, ptrs)) {
free_file_to_struct_resouces(handle);
return oops;
}
...
return success;
zabierz
Lodowe procedury wskazują błąd jakoś . Procedury wyższego poziomu mogą wyjść z kodu w razie potrzeby.
To zależy od szerszych okoliczności. W przypadku niektórych programów po prostu przerwanie jest właściwą rzecz do zrobienia.
W przypadku niektórych aplikacji, właściwą rzecz jest skurczenie pamięci podręcznej i spróbuj ponownie malloc
. W przypadku niektórych programów wielowymiarowych, po prostu czekając (aby dać inne wątki szansę na darmową pamięć) i ponowienie będzie działać.
W przypadku aplikacji, które muszą być bardzo niezawodne, potrzebujesz rozwiązania poziomu aplikacji. Jedno rozwiązanie, które użyłem i testowane bitwa jest to:
- Mieć awaryjną pulę pamięci przydzielonej na starcie.
- Jeśli
malloc
nie powiedzie się, bezpłatnie pulę awaryjną. - W przypadku połączeń, które nie mogą mieć pewności za pomocą odpowiedzi
NULL
, snu i ponów próbę. - Mieć wątek serwisowy, który próbuje uzupełnić basen awaryjny.
- Mieć kod, który wykorzystuje buforowanie odpowiadać na niepełny pulę awaryjną, zmniejszając zużycie pamięci.
- Jeśli masz możliwość rzucenia obciążenia, na przykład, przesuwając ładunek do innych instancji, zrób to, jeśli pula awaryjna nie jest pełna.
- W przypadku działań uznaniowych, które wymagają przydzielania wielu pamięci, sprawdź poziom basenu awaryjnego i nie wykonaj akcji, jeśli nie jest to pełne ani blisko.
- Jeśli basen awaryjny staje się pusty, przerwij.
Podobne pytania
Nowe pytania
c
C jest językiem programowania ogólnego przeznaczenia, używanym do programowania systemów (system operacyjny i systemy wbudowane), bibliotek, gier i wielu platform. Ten znacznik powinien być używany w przypadku ogólnych pytań dotyczących języka C, zgodnie z definicją w standardzie ISO 9899 (najnowsza wersja 9899: 2018, chyba że określono inaczej - również oznaczanie żądań dotyczących wersji za pomocą c89, c99, c11 itd.). C różni się od C ++ i nie należy go łączyć ze znacznikiem C ++ bez racjonalnego powodu.