W mojej firmie obowiązuje zasada kodowania, która mówi, że po zwolnieniu pamięci należy zresetować zmienną do NULL
. Na przykład ...
void some_func ()
{
int *nPtr;
nPtr = malloc (100);
free (nPtr);
nPtr = NULL;
return;
}
Czuję, że w przypadkach takich jak w kodzie pokazanym powyżej, ustawienie na NULL
nie ma żadnego znaczenia. A może coś mi brakuje?
Jeśli nie ma to sensu w takich przypadkach, zamierzam porozmawiać z „zespołem ds. Jakości” o usunięcie tej reguły kodowania. Proszę o poradę.
c
coding-style
malloc
free
heap-memory
Alphaneo
źródło
źródło
ptr == NULL
przed zrobieniem cokolwiek z tym. Jeśli nie anulujesz swoich bezpłatnych wskaźników, otrzymasz,ptr != NULL
ale nadal nieużywalny wskaźnik.Odpowiedzi:
Ustawienie nieużywanych wskaźników na NULL jest stylem obronnym, chroniącym przed wiszącymi błędami wskaźnika. W przypadku uzyskania dostępu do wiszącego wskaźnika po jego zwolnieniu można odczytać lub nadpisać pamięć losową. W przypadku uzyskania dostępu do pustego wskaźnika w większości systemów występuje natychmiastowa awaria, informując od razu o błędzie.
W przypadku zmiennych lokalnych może to być trochę bezcelowe, jeśli jest „oczywiste”, że wskaźnik nie jest już używany po zwolnieniu, więc ten styl jest bardziej odpowiedni dla danych składowych i zmiennych globalnych. Nawet w przypadku zmiennych lokalnych dobrym podejściem może być kontynuacja funkcji po zwolnieniu pamięci.
Aby ukończyć styl, powinieneś również zainicjować wskaźniki na NULL, zanim zostaną przypisane im prawdziwa wartość wskaźnika.
źródło
int *nPtr=NULL;
. Teraz zgodziłbym się, że byłoby to zbędne, z malloc po prawej stronie w następnej linii. Jeśli jednak między deklaracją a pierwszą inicjalizacją znajduje się kod, ktoś może zacząć używać zmiennej, mimo że nie ma ona jeszcze wartości. Jeśli zainicjujesz wartość null, otrzymasz segfault; bez, możesz ponownie czytać lub zapisywać pamięć losową. Podobnie, jeśli zmienna zostanie później zainicjalizowana tylko warunkowo, późniejsze błędne dostępy powinny spowodować natychmiastowe awarie, jeśli pamiętasz o inicjalizacji zerowej.Ustawienie wskaźnika na
NULL
afterfree
jest wątpliwą praktyką, która jest często spopularyzowana jako zasada „dobrego programowania” oparta na ewidentnie fałszywej przesłance. To jedna z tych fałszywych prawd, które należą do kategorii „brzmi dobrze”, ale w rzeczywistości nie przynoszą absolutnie nic pożytecznego (i czasami prowadzą do negatywnych konsekwencji).Podobno ustawienie wskaźnika na
NULL
afterfree
ma zapobiec przerażającemu problemowi „podwójnego zwolnienia”, gdy ta sama wartość wskaźnika jest przekazywanafree
więcej niż raz. W rzeczywistości jednak w 9 przypadkach na 10 rzeczywisty problem „podwójnego zwolnienia” występuje, gdy różne obiekty wskaźnika posiadające tę samą wartość wskaźnika są używane jako argumenty forfree
. Nie trzeba dodawać, że ustawienie wskaźnika naNULL
after niefree
daje absolutnie nic, co mogłoby zapobiec problemowi w takich przypadkach.Oczywiście można napotkać problem „podwójnego zwolnienia”, używając tego samego obiektu wskaźnika jako argumentu funkcji
free
. Jednak w rzeczywistości sytuacje takie jak ta zwykle wskazują na problem z ogólną logiczną strukturą kodu, a nie na zwykłe przypadkowe „podwójne wolne”. W takich przypadkach właściwym sposobem rozwiązania problemu jest przejrzenie i przemyślenie struktury kodu w celu uniknięcia sytuacji, w której ten sam wskaźnik jest przekazywanyfree
więcej niż raz. W takich przypadkach ustawienie wskaźnikaNULL
i uznanie problemu za „naprawiony” to nic innego jak próba zamiatania problemu pod dywan. Po prostu nie zadziała w ogólnym przypadku, ponieważ problem ze strukturą kodu zawsze znajdzie inny sposób, aby się ujawnić.Wreszcie, jeśli Twój kod jest specjalnie zaprojektowany, aby polegać na wartości wskaźnika będącej
NULL
lub nieNULL
, ustawienie wartości wskaźnika naNULL
after jest całkowicie w porządkufree
. Ale zgodnie z ogólną zasadą „dobrej praktyki” (jak w przypadku „zawsze ustawiaj wskaźnik naNULL
pofree
”) jest to znowu dobrze znana i całkiem bezużyteczna podróbka, często stosowana z czysto religijnych powodów podobnych do voodoo.źródło
foo* bar=getFoo(); /*more_code*/ free(bar); /*more_code*/ return bar != NULL;
. Tutaj ustawieniebar
abyNULL
po wywołaniufree
spowoduje funkcję myśleć, że nigdy nie miał bar i zwróci błędną wartość!Większość odpowiedzi skupiała się na zapobieganiu podwójnemu zwolnieniu, ale ustawienie wskaźnika na NULL ma jeszcze jedną korzyść. Po zwolnieniu wskaźnika pamięć ta jest dostępna do ponownego przydzielenia przez kolejne wywołanie malloc. Jeśli nadal masz oryginalny wskaźnik dookoła, możesz skończyć z błędem, w którym będziesz próbował użyć wskaźnika po zwolnieniu i uszkodzeniu innej zmiennej, a wtedy twój program wejdzie w nieznany stan i mogą się zdarzyć różne rodzaje złych rzeczy (awaria, jeśli masz szczęście, uszkodzenie danych, jeśli masz pecha). Gdybyś ustawił wskaźnik na NULL po zwolnieniu, każda próba odczytu / zapisu przez ten wskaźnik później spowodowałaby błąd segfault, który jest generalnie lepszy niż przypadkowe uszkodzenie pamięci.
Z obu powodów dobrym pomysłem może być ustawienie wskaźnika na NULL po funkcji free (). Jednak nie zawsze jest to konieczne. Na przykład, jeśli zmienna wskaźnikowa wychodzi poza zakres natychmiast po funkcji free (), nie ma powodu, aby ustawiać ją na NULL.
źródło
free
, ale to ma sens.Jest to uważane za dobrą praktykę, aby uniknąć nadpisywania pamięci. W powyższej funkcji jest to niepotrzebne, ale często kiedy jest to zrobione, może znaleźć błędy aplikacji.
Zamiast tego spróbuj czegoś takiego:
DEBUG_VERSION umożliwia zwolnienie profilu w kodzie debugowania, ale oba są funkcjonalnie takie same.
Edycja : Dodano do ... podczas gdy zgodnie z sugestią poniżej, dzięki.
źródło
do { } while(0)
bloku, abyif(x) myfree(x); else dostuff();
się nie zepsuło.do {X} while (0)
to IMO najlepszy sposób na stworzenie ciała makra, które „czuje się i działa jak” funkcja. Większość kompilatorów i tak optymalizuje pętlę.Jeśli dojdziesz do wskaźnika, który był wolny () d, może się zepsuć lub nie. Ta pamięć może zostać ponownie przydzielona do innej części programu, a następnie wystąpi uszkodzenie pamięci,
Jeśli ustawisz wskaźnik na NULL, a następnie uzyskasz do niego dostęp, program zawsze zawiesza się z błędem segfault. Koniec z `` czasami działa '', koniec z `` awariami w nieprzewidywalny sposób ''. O wiele łatwiej jest debugować.
źródło
Ustawienie wskaźnika na
free
pamięć 'd oznacza, że każda próba uzyskania dostępu do tej pamięci za pośrednictwem wskaźnika spowoduje natychmiastową awarię, zamiast powodować niezdefiniowane zachowanie. Znacznie ułatwia określenie, gdzie coś poszło nie tak.Widzę twój argument: ponieważ
nPtr
zaraz potem wychodzi poza zakresnPtr = NULL
, nie ma powodu, aby go ustawićNULL
. Jednak w przypadku elementustruct
członkowskiego lub w innym miejscu, w którym wskaźnik nie wychodzi natychmiast poza zakres, ma to większy sens. Nie jest od razu jasne, czy ten wskaźnik zostanie ponownie użyty przez kod, który nie powinien go używać.Prawdopodobnie reguła została podana bez rozróżnienia między tymi dwoma przypadkami, ponieważ znacznie trudniej jest automatycznie egzekwować regułę, nie mówiąc już o przestrzeganiu jej przez programistów. Nie zaszkodzi ustawiać wskaźniki
NULL
po każdym wolnym, ale może wskazywać na duże problemy.źródło
najczęstszym błędem w c jest podwójne wolne. Zasadniczo robisz coś takiego
i kończy się dość źle, system operacyjny próbuje uwolnić część już zwolnionej pamięci i generalnie powoduje awarię. Więc dobrą praktyką jest ustawienie na
NULL
, abyś mógł wykonać test i sprawdzić, czy naprawdę potrzebujesz zwolnić tę pamięćnależy również zauważyć, że
free(NULL)
nic nie da, więc nie musisz pisać instrukcji if. Nie jestem guru systemów operacyjnych, ale nawet teraz jestem ładna większość systemów operacyjnych na podwójnej wolności.Jest to również główny powód, dla którego wszystkie języki z wyrzucaniem elementów bezużytecznych (Java, dotnet) były tak dumne, że nie mają tego problemu, a także nie muszą pozostawiać programistom zarządzania pamięcią jako całości.
źródło
p = (char *)malloc(.....); free(p); if(p!=null) //p!=null is true, p is not null although freed { free(p); //Note: checking doesnot prevent error here }
free(void *ptr)
nie można zmienić wartości przekazywanego wskaźnika. Może zmienić zawartość wskaźnika, dane przechowywane pod tym adresem , ale nie sam adres ani wartość wskaźnika . Wymagałoby tofree(void **ptr)
(co najwyraźniej nie jest dozwolone przez standard) lub makra (które jest dozwolone i doskonale przenośne, ale ludzie nie lubią makr). Ponadto w C nie chodzi o wygodę, ale o zapewnienie programistom takiej kontroli, jaką chcą. Jeśli nie chcą dodatkowego narzutu ustawiania wskaźnikówNULL
, nie należy ich narzucać.free
” (razem z takimi rzeczami jak „rzutowanie wyniku funkcji alokacji pamięci” lub „bezmyślne używanie nazw typów zsizeof
”).Ideą tego jest powstrzymanie przypadkowego ponownego użycia uwolnionego wskaźnika.
źródło
To (może) faktycznie być ważne. Chociaż zwalniasz pamięć, późniejsza część programu może przydzielić coś nowego, co zdarzy się wylądować w przestrzeni. Twój stary wskaźnik wskazywałby teraz prawidłowy fragment pamięci. Jest wtedy możliwe, że ktoś użyje wskaźnika, co spowoduje nieprawidłowy stan programu.
Jeśli wyzerujesz wskaźnik, każda próba jego użycia spowoduje wyłuskiwanie 0x0 i zawieszenie się w tym miejscu, co jest łatwe do debugowania. Przypadkowe wskaźniki wskazujące na pamięć losową są trudne do debugowania. Oczywiście nie jest to konieczne, ale dlatego jest to dokument zawierający najlepsze praktyki.
źródło
Ze standardu ANSI C:
„niezdefiniowane zachowanie” to prawie zawsze awaria programu. Aby tego uniknąć, można bezpiecznie zresetować wskaźnik do NULL. Funkcja free () nie może tego zrobić, ponieważ jest przekazywana tylko wskaźnik, a nie wskaźnik do wskaźnika. Możesz także napisać bezpieczniejszą wersję free (), która zeruje wskaźnik:
źródło
NULL
aby uniknąć maskowania błędów. stackoverflow.com/questions/1025589/… Wygląda na to, że w obu przypadkach niektóre błędy zostały ukryte.Uważam, że jest to niewielka pomoc, ponieważ z mojego doświadczenia wynika, że kiedy ludzie uzyskują dostęp do zwolnionej alokacji pamięci, prawie zawsze dzieje się tak, ponieważ mają gdzieś inny wskaźnik do tego. A potem jest to sprzeczne z innym osobistym standardem kodowania, który brzmi „Unikaj niepotrzebnego bałaganu”, więc nie robię tego, ponieważ myślę, że rzadko pomaga i sprawia, że kod jest nieco mniej czytelny.
Jednak - nie ustawię zmiennej na null, jeśli wskaźnik nie ma być ponownie użyty, ale często projekt wyższego poziomu daje mi powód, aby i tak ustawić ją na null. Na przykład jeśli wskaźnik jest członkiem klasy i usunąłem to, na co wskazuje, wówczas "kontrakt", jeśli podoba ci się klasa, jest taki, że ten element członkowski wskaże coś ważnego w dowolnym momencie, więc musi być ustawiony na null z tego powodu. Małe rozróżnienie, ale myślę, że ważne.
W języku c ++ ważne jest, aby zawsze myśleć, kto jest właścicielem tych danych, gdy przydzielasz trochę pamięci (chyba że używasz inteligentnych wskaźników, ale nawet wtedy wymagana jest pewna myśl). Ten proces zwykle prowadzi do tego, że wskaźniki na ogół należą do jakiejś klasy i generalnie chcesz, aby klasa była w stanie przez cały czas, a najłatwiejszym sposobem jest ustawienie zmiennej składowej na NULL, aby wskazać, że wskazuje do niczego teraz.
Wspólny wzór jest ustawienie wszystkich wskazówek państw NULL w konstruktorze i mieć wywołanie destruktora usuwania na wszelkie wskaźniki do danych, że projekt mówi, że klasa jest właścicielem . Oczywiście w tym przypadku musisz ustawić wskaźnik na NULL, gdy usuniesz coś, aby wskazać, że wcześniej nie jesteś właścicielem żadnych danych.
Podsumowując, tak, często ustawiam wskaźnik na NULL po usunięciu czegoś, ale jest to część większego projektu i przemyśleń na temat tego, kto jest właścicielem danych, a nie ślepo przestrzeganie standardowej reguły kodowania. Nie zrobiłbym tego na twoim przykładzie, ponieważ myślę, że nie ma z tego żadnej korzyści i dodaje to „bałaganu”, który z mojego doświadczenia jest tak samo odpowiedzialny za błędy i zły kod, jak tego typu rzeczy.
źródło
Ostatnio natknąłem się na to samo pytanie po tym, jak szukałem odpowiedzi. Doszedłem do takiego wniosku:
Jest to najlepsza praktyka i należy ją stosować, aby była przenośna we wszystkich (wbudowanych) systemach.
free()
jest funkcją biblioteczną, która zmienia się wraz ze zmianą platformy, więc nie należy oczekiwać, że po przekazaniu wskaźnika do tej funkcji i zwolnieniu pamięci, wskaźnik ten zostanie ustawiony na NULL. Może tak nie być w przypadku niektórych bibliotek zaimplementowanych na platformę.więc zawsze idź
źródło
Ta reguła jest przydatna, gdy próbujesz uniknąć następujących scenariuszy:
1) Masz naprawdę długą funkcję ze skomplikowaną logiką i zarządzaniem pamięcią i nie chcesz przypadkowo ponownie użyć wskaźnika do usuniętej pamięci w dalszej części funkcji.
2) Wskaźnik jest zmienną składową klasy, która ma dość złożone zachowanie i nie chcesz przypadkowo ponownie użyć wskaźnika do usuniętej pamięci w innych funkcjach.
W twoim scenariuszu nie ma to większego sensu, ale jeśli funkcja miałaby się wydłużyć, może to mieć znaczenie.
Możesz argumentować, że ustawienie go na NULL może faktycznie maskować później błędy logiczne lub w przypadku, gdy zakładasz, że jest poprawne, nadal zawieszasz się na NULL, więc nie ma to znaczenia.
Ogólnie radziłbym ustawić go na NULL, gdy uważasz, że to dobry pomysł, i nie przejmować się, gdy uważasz, że nie jest tego wart. Zamiast tego skup się na pisaniu krótkich funkcji i dobrze zaprojektowanych klas.
źródło
Aby dodać do tego, co powiedzieli inni, jedną dobrą metodą użycia wskaźnika jest zawsze sprawdzanie, czy jest to prawidłowy wskaźnik, czy nie. Coś jak:
Jawne oznaczenie wskaźnika jako NULL po zwolnieniu pozwala na tego rodzaju użycie w C / C ++.
źródło
To może być bardziej argument do zainicjowania wszystkich wskaźników na NULL, ale coś takiego może być bardzo podstępnym błędem:
p
kończy się w tym samym miejscu na stosie, co poprzedninPtr
, więc nadal może zawierać pozornie prawidłowy wskaźnik. Przypisanie do*p
może nadpisać wszelkiego rodzaju niepowiązane rzeczy i prowadzić do brzydkich błędów. Zwłaszcza jeśli kompilator inicjuje zmienne lokalne z zerem w trybie debugowania, ale nie po włączeniu optymalizacji. Więc kompilacje debugowania nie wykazują żadnych oznak błędu, podczas gdy kompilacje wydań wybuchają losowo ...źródło
Ustawienie wskaźnika, który właśnie został zwolniony, na NULL nie jest obowiązkowe, ale jest dobrą praktyką. W ten sposób można uniknąć 1) używania uwolnionego szpicu 2) uwolnienia go
źródło
Ustawienie wskaźnika na NULL ma na celu ochronę przed tzw. Double-free - sytuacja, w której funkcja free () jest wywoływana więcej niż jeden raz dla tego samego adresu bez ponownego przydzielania bloku pod tym adresem.
Podwójne zwolnienie prowadzi do niezdefiniowanego zachowania - zwykle powoduje uszkodzenie sterty lub natychmiastową awarię programu. Wywołanie free () dla wskaźnika NULL nic nie robi i dlatego jest gwarantowane, że jest bezpieczne.
Więc najlepszą praktyką, chyba że teraz jesteś pewien, że wskaźnik opuści zasięg natychmiast lub wkrótce po funkcji free (), jest ustawienie tego wskaźnika na NULL, aby nawet jeśli funkcja free () zostanie ponownie wywołana, zostanie teraz wywołana dla wskaźnika NULL i niezdefiniowanego zachowania jest omijany.
źródło
Chodzi o to, że jeśli spróbujesz wyłuskać nieaktualny wskaźnik po jego zwolnieniu, chcesz zakończyć się niepowodzeniem (segfault), a nie cicho i tajemniczo.
Ale bądź ostrożny. Nie wszystkie systemy powodują segfault, jeśli wyłuskujesz wartość NULL. W (przynajmniej niektórych wersjach) AIX, * (int *) 0 == 0, a Solaris ma opcjonalną zgodność z tą "funkcją" systemu AIX.
źródło
Do pierwotnego pytania: ustawienie wskaźnika na NULL bezpośrednio po zwolnieniu zawartości jest całkowitą stratą czasu, pod warunkiem, że kod spełnia wszystkie wymagania, jest w pełni debugowany i nigdy nie zostanie ponownie zmodyfikowany. Z drugiej strony defensywne NULLowanie zwolnionego wskaźnika może być całkiem przydatne, gdy ktoś bezmyślnie doda nowy blok kodu pod free (), gdy projekt oryginalnego modułu nie jest poprawny, a w takim przypadku -kompiluje-ale-nie-robi-czego-chcę-bugi.
W każdym systemie istnieje nieosiągalny cel, jakim jest uczynienie go najłatwiejszym do wykonania właściwej rzeczy, oraz nieredukowalny koszt niedokładnych pomiarów. W C oferujemy zestaw bardzo ostrych, bardzo mocnych narzędzi, które mogą stworzyć wiele rzeczy w rękach wykwalifikowanego pracownika i zadać różnego rodzaju metaforyczne obrażenia, gdy są niewłaściwie obsługiwane. Niektóre są trudne do zrozumienia lub prawidłowego użycia. A ludzie, mając naturalną awersję do ryzyka, robią irracjonalne rzeczy, takie jak sprawdzanie wskaźnika dla wartości NULL, zanim zadzwonią za darmo…
Problem z pomiarem polega na tym, że ilekroć próbujesz oddzielić dobre od gorszych, im bardziej złożony przypadek, tym większe prawdopodobieństwo, że otrzymasz niejednoznaczny pomiar. Jeśli celem jest trzymanie się tylko dobrych praktyk, to niektóre niejednoznaczne zostają wyrzucone z faktycznie złymi. JEŚLI Twoim celem jest wyeliminowanie tego, co złe, to niejasności mogą pozostać przy dobru. Te dwa cele, zachowaj tylko dobre lub wyeliminuj wyraźnie złe, wydają się być diametralnie przeciwstawne, ale zwykle istnieje trzecia grupa, która nie jest ani jednym, ani drugim, niektóre z obu.
Zanim przedstawisz sprawę z działem jakości, spróbuj przejrzeć bazę danych błędów, aby zobaczyć, jak często, jeśli w ogóle, nieprawidłowe wartości wskaźników powodowały problemy, które trzeba było zapisać. Jeśli chcesz coś zmienić, zidentyfikuj najczęstszy problem w swoim kodzie produkcyjnym i zaproponuj trzy sposoby, aby temu zapobiec
źródło
Istnieją dwa powody:
Unikaj awarii podczas podwójnego zwalniania
Napisane przez RageZ w zduplikowanym pytaniu .
Unikaj używania już zwolnionych wskaźników
Napisane przez Martina v. Löwisa w innej odpowiedzi .
źródło
Ponieważ masz na miejscu zespół ds. Zapewnienia jakości, pozwolę sobie dodać drobną kwestię dotyczącą kontroli jakości. Niektóre zautomatyzowane narzędzia kontroli jakości dla C oznaczają przypisania do zwolnionych wskaźników jako „bezużyteczne przypisanie do
ptr
”. Na przykład mówi PC-lint / FlexeLint z Gimpel Softwaretst.c 8 Warning 438: Last value assigned to variable 'nPtr' (defined at line 5) not used
Istnieją sposoby na selektywne pomijanie komunikatów, więc nadal możesz spełnić oba wymagania dotyczące kontroli jakości, jeśli Twój zespół tak zdecyduje.
źródło
Zawsze dobrze jest zadeklarować zmienną wskaźnikową z wartością NULL, taką jak,
Powiedzmy, że ptr wskazuje na adres pamięci 0x1000 . Po użyciu
free(ptr)
zawsze zaleca się anulowanie zmiennej wskaźnika przez ponowne zadeklarowanie jej na NULL . na przykład:Jeśli nie zostanie ponownie zadeklarowana na NULL , zmienna wskaźnikowa nadal wskazuje ten sam adres ( 0x1000 ), ta zmienna wskaźnikowa jest nazywana wskaźnikiem wiszącym . Jeśli zdefiniujesz inną zmienną wskaźnika (powiedzmy, q ) i dynamicznie przydzielisz adres nowemu wskaźnikowi, istnieje szansa, że ten sam adres ( 0x1000 ) zostanie przyjęty przez nową zmienną wskaźnika. Jeśli w przypadku, użyjesz tego samego wskaźnika ( ptr ) i zaktualizujesz wartość pod adresem wskazywanym przez ten sam wskaźnik ( ptr ), to program zakończy zapisywanie wartości w miejscu, na które wskazuje q (ponieważ p i q są wskazujące na ten sam adres (0x1000 )).
na przykład
źródło
Krótko mówiąc: nie chcesz przypadkowo (przez pomyłkę) uzyskać dostępu do uwolnionego adresu. Ponieważ zwalniając adres, zezwalasz na przydzielenie tego adresu w stercie do innej aplikacji.
Jeśli jednak nie ustawisz wskaźnika na NULL i przez pomyłkę spróbuj usunąć odniesienie do wskaźnika lub zmienić wartość tego adresu; WCIĄŻ MOŻESZ TO ZROBIĆ. ALE NIE CZEGO CHCIAŁBYŚ ZROBIĆ.
Dlaczego nadal mogę uzyskać dostęp do zwolnionej lokalizacji pamięci? Ponieważ: Być może zwolniono pamięć, ale zmienna wskaźnika nadal zawierała informacje o adresie pamięci sterty. Tak więc, jako strategia obronna, ustaw ją na NULL.
źródło