Czy można bezpiecznie usunąć wskaźnik NULL?

299

Czy można bezpiecznie usunąć wskaźnik NULL?

I czy to dobry styl kodowania?

qiuxiafei
źródło
21
Dobrą praktyką jest pisanie programów w C ++ bez jednego wywołania delete. Zamiast tego użyj RAII . Oznacza to, że używaj std::vector<T> v(100);zamiast T* p = new T[100];, używaj inteligentnych wskaźników takich jak unique_ptr<T>i, shared_ptr<T>które zajmują się usuwaniem zamiast surowych wskaźników itp.
Fredoverflow
8
dzięki make_shared(c ++ 11) i make_unique(C ++ 14) Twój program powinien zawierać zera z newidelete
sp2danny
2
Nadal mogą występować rzadkie przypadki, które wymagają nowego / usunięcia, np. Atomowy <T *>: atomowy <unikalny_ptr <T>> jest niedozwolony, a atomowy <wspólny_ptr <T>> ma narzut, który może być niedopuszczalny w niektórych przypadkach.
atb
2
Aby zadeklarować klasę z zarządzaniem zasobami za pomocą RAII, musisz wywołać new i usunąć prawo ?, lub mówisz, że istnieje klasa szablonów, która mogłaby to ukryć.
VinGarcia
2
@ VinGarcia Chodzi o to, że większość kodu użytkownika / klienta (to znaczy nie-bibliotecznego) nigdy nie powinna pisać newlub delete. Klasy zaprojektowane do zarządzania zasobami, w których komponenty standardowe nie mogą wykonać zadania, mogą oczywiście robić to, co muszą, ale chodzi o to, że robią brzydkie rzeczy z zarządzaną pamięcią, a nie kodem użytkownika końcowego. Stwórz więc własną bibliotekę / klasę pomocniczą do zrobienia new/ deletei używaj tej klasy zamiast nich.
underscore_d

Odpowiedzi:

265

deletei tak wykonuje sprawdzenie, więc sprawdzenie go z boku powoduje zwiększenie kosztów i wygląda brzydiej. Bardzo dobrą praktyką jest ustawianie wskaźnika NULL po delete(pomaga uniknąć podwójnego usunięć i inne podobne problemy korupcyjne pamięci).

Chciałbym też, gdyby deletedomyślnie ustawiał parametr na NULL jak w

#define my_delete(x) {delete x; x = NULL;}

(Wiem o wartościach R i L, ale czy nie byłoby miło?)

Ruslik
źródło
72
Zauważ, że nadal może istnieć kilka innych wskaźników wskazujących na ten sam obiekt, nawet jeśli podczas usuwania ustawisz jeden na NULL.
sth
13
W większości przypadków w moim kodzie wskaźnik wykracza poza zasięg po usunięciu. Znacznie bezpieczniejsze niż ustawienie na NULL.
lipiec
142
Bardzo praktyka bóg nie ustawiając wskaźnik na NULL po kasowania. Ustawienie wskaźnika na NULL po usunięciu powoduje maskowanie błędów przydziału pamięci, co jest bardzo złe. Program, który jest poprawny, nie usuwa wskaźnika dwukrotnie, a program, który usuwa wskaźnik dwukrotnie, powinien ulec awarii.
Damon
15
@Alice: Nie ma znaczenia to, co mówi standard w tym względzie. Standard zdefiniował usunięcie wskaźnika zerowego, który jest ważny z jakiegoś absurdalnego powodu 30 lat temu, więc jest legalny (najprawdopodobniej spuścizna C). Ale dwukrotne usunięcie tego samego wskaźnika (nawet po zmianie wzorca bitowego) nadal jest poważnym błędem programu. Nie przez brzmienie standardu, ale przez logikę programu i własność. Podobnie jak usuwanie wskaźnika zerowego, ponieważ wskaźnik zerowy nie odpowiada żadnemu obiektowi , więc nic nie może zostać usunięte. Program musi dokładnie wiedzieć, czy obiekt jest prawidłowy i kto jest jego właścicielem oraz kiedy można go usunąć.
Damon
27
@Damon Jednak pomimo tych zniesień drakońskich zasad własności, struktury wolne od zamków są znacznie solidniejsze niż te oparte na zamkach. I tak, moi współpracownicy naprawdę mnie uwielbiają za ulepszony profil wykonania, który zapewniają te struktury i rygorystyczne bezpieczeństwo wątków, które utrzymują, co pozwala łatwiej zrozumieć kod (świetne do konserwacji). Jednak żaden z tych ani twój dorozumiany atak osobisty nie ma związku z jakąkolwiek definicją poprawności, ważności lub własności. To, co proponujesz, jest dobrą regułą, ale nie jest to uniwersalne prawo ani nie jest ono zapisane w normie.
Alice
77

Z wersji standardowej C ++ 0x.

5.3.5 USD / 2 - „[...] W obu przypadkach wartością argumentu operacji usuwania może być wartość wskaźnika zerowego. [...” ”

Oczywiście nikt nigdy nie zrobiłby „usunięcia” wskaźnika o wartości NULL, ale jest to bezpieczne. Idealnie byłoby, gdyby nie było kodu usuwającego wskaźnik NULL. Ale czasami jest to przydatne, gdy usuwanie wskaźników (np. W kontenerze) odbywa się w pętli. Ponieważ usuwanie wartości wskaźnika NULL jest bezpieczne, można naprawdę napisać logikę usuwania bez jawnego sprawdzania, czy operand NULL ma zostać usunięty.

Nawiasem mówiąc, C Standard 7.20.3.2 również mówi, że „wolny” na wskaźniku NULL nie wykonuje żadnej akcji.

Funkcja swobodna powoduje zwolnienie miejsca wskazywanego przez ptr, czyli udostępnienie go do dalszego przydziału. Jeśli ptr jest wskaźnikiem zerowym, nie występuje żadna akcja.

Chubsdad
źródło
2
Naprawdę chciałbym tę odpowiedź za jej cytowania, gdyby nie celowo wprowadziła nieefektywności w niezoptymalizowanym kodzie. Jak stwierdzono w przyjętej odpowiedzi, usunięcie wskaźnika zerowego nie jest możliwe. Dlatego sprawdzenie, czy wskaźnik ma wartość zerową przed usunięciem, jest całkowicie obce.
codetaku
47

Tak, jest bezpiecznie.

Usunięcie wskaźnika zerowego nie szkodzi; często zmniejsza liczbę testów na końcu funkcji, jeśli nieprzydzielone wskaźniki są inicjowane do zera, a następnie po prostu usuwane.


Ponieważ poprzednie zdanie spowodowało zamieszanie, przykład - co nie jest wyjątkowe bezpieczne - tego, co jest opisane:

void somefunc(void)
{
    SomeType *pst = 0;
    AnotherType *pat = 0;

    
    pst = new SomeType;
    
    if (…)
    {
        pat = new AnotherType[10];
        
    }
    if (…)
    {
        code using pat sometimes
    }

    delete[] pat;
    delete pst;
}

Istnieje wiele rodzajów nitów, które można wybrać za pomocą przykładowego kodu, ale koncepcja jest (mam nadzieję) jasna. Zmienne wskaźnikowe są inicjowane na zero, dzięki czemu deleteoperacje na końcu funkcji nie muszą sprawdzać, czy nie są one zerowe w kodzie źródłowym; kod biblioteki i tak sprawdza to.

Jonathan Leffler
źródło
Musiałem to przeczytać kilka razy, żeby to zrozumieć. Masz na myśli inicjalizację ich do zera u góry metody, czy też podczas niej, a nie na pewno, na pewno? W przeciwnym razie po prostu usuniesz zarówno zerowanie, jak i usuniesz.
Markiz Lorne
1
@EJP: a nie całkowicie nieprawdopodobne zarys funkcji może być: void func(void) { X *x1 = 0; Y *y1 = 0; … x1 = new[10] X; … y1 = new[10] Y; … delete[] y1; delete[] x1; }. Nie pokazałem żadnej struktury bloku ani skoków, ale delete[]operacje na końcu są bezpieczne ze względu na inicjalizacje na początku. Jeśli coś przeskoczyło do końca po tym, jak x1zostało przydzielone, a wcześniej y1zostało przydzielone, i nie było inicjalizacji y1, to zachowanie byłoby niezdefiniowane - i chociaż kod mógł sprawdzać pod kątem nieważności (z x1i y1) przed usunięciem, nie trzeba tego robić więc.
Jonathan Leffler,
22

Usunięcie wskaźnika zerowego nie ma wpływu. To niekoniecznie dobry styl kodowania, ponieważ nie jest potrzebny, ale też nie jest zły.

Jeśli szukasz dobrych praktyk kodowania, rozważ użycie zamiast tego inteligentnych wskaźników, więc wcale nie musisz delete.

Brian R. Bondy
źródło
20
ludzie chcą usunąć wskaźnik NULL, gdy nie są pewni, czy zawiera on NULL ... gdyby wiedzieli, że to NULL, nie zastanawialiby się nad usunięciem i dlatego pytali ;-).
Tony Delroy
@ Tony: Chodziło mi tylko o to, że nie przyniesie żadnego efektu, a obecność takiego kodu, który usuwa wskaźnik, który czasami zawiera NULL, niekoniecznie jest zła.
Brian R. Bondy
3
Zbędne kontrole IMO z pewnością są złe, jeśli chodzi o wydajność, czytelność i łatwość konserwacji.
paulm
@paulm OP z pewnością nie mówi o złym rodzaju, bardziej o rodzaju Seg Fault / UB.
Zima
8

Aby uzupełnić odpowiedź ruslika , w C ++ 14 możesz użyć tej konstrukcji:

delete std::exchange(heapObject, nullptr);
ashrasmun
źródło
3

Jest bezpieczny, chyba że przeciąłeś operatora usuwania. jeśli przeciąłeś operatora usuwania i nie obsługujesz warunku zerowego, to nie jest to wcale bezpieczne.


źródło
Czy możesz dodać jakieś wyjaśnienie dla swojej odpowiedzi?
Marcin Nabiałek
-2

Przekonałem się, że usunięcie [] NULL (tj. Składnia tablicy) nie jest bezpieczne (VS2010). Nie jestem pewien, czy jest to zgodne ze standardem C ++.

To jest bezpiecznie usunąć NULL (składni skalarne).

fenest
źródło
6
To jest nielegalne i nie wierzę w to.
Konrad Rudolph
5
Powinieneś być w stanie usunąć dowolny wskaźnik zerowy. Więc jeśli to się psuje, prawdopodobnie masz kod w kodzie, który to pokazuje.
Mysticial
3
§5.3.2 W drugim wariancie (tablica kasowania) wartością argumentu kasowania może być zerowa wartość wskaźnika lub wartość wskaźnika wynikająca z poprzedniego wyrażenia nowej tablicy.
sp2danny,
1
@Opux Zachowanie VS2010 zarzucane w odpowiedzi. Jak wyjaśniono w innych komentarzach, jest to bezpieczne delete[] NULL.
Konrad Rudolph
1
@Opux Właśnie dlatego napisałem „Nie wierzę w to”, a nie „to źle”. Ale nadal tego nie robię i byłoby to dość skandaliczne, głupie naruszenie standardu. VC ++ jest ogólnie całkiem niezły w przestrzeganiu ograniczeń standardu, a miejsca, w których je narusza, mają sens historyczny.
Konrad Rudolph