Sprawdzanie wskaźnika NULL w C / C ++ [zamknięte]


153

W ostatnim przeglądzie kodu współtwórca próbuje wymusić, aby wszystkie NULLsprawdzenia wskaźników były wykonywane w następujący sposób:

int * some_ptr;
// ...
if (some_ptr == NULL)
{
    // Handle null-pointer error
}
else
{
    // Proceed
}

zamiast

int * some_ptr;
// ...
if (some_ptr)
{
    // Proceed
}
else
{
    // Handle null-pointer error
}

Zgadzam się, że jego sposób jest nieco bardziej przejrzysty w tym sensie, że wyraźnie mówi „Upewnij się, że ten wskaźnik nie ma wartości NULL”, ale chciałbym temu przeciwdziałać, mówiąc, że każdy, kto pracuje nad tym kodem, zrozumiałby, że użycie zmiennej wskaźnika w ifinstrukcja niejawnie sprawdzaNULL . Uważam również, że druga metoda ma mniejsze szanse na wprowadzenie błędu podobnego:

if (some_ptr = NULL)

co jest absolutnym trudem do znalezienia i debugowania.

Który sposób wolisz i dlaczego?


32
Posiadanie spójnego stylu jest czasami ważniejsze niż wybrany styl.
Mark Ransom

15
@Mark: Konsekwencja jest zawsze najważniejszym czynnikiem Twojego stylu.
sbi

13
„Wydaje mi się również, że druga metoda ma mniejsze szanse na wprowadzenie błędu podobnego: if (some_ptr = NULL)”. Dlatego niektórzy używają if (NULL == some_ptr), nie mogą przypisać wartości NULL, aby uzyskać błąd kompilacji.
user122302

14
Większość kompilatorów w dzisiejszych czasach ostrzega przed przypisaniem w warunkach warunkowych - niestety zbyt wielu programistów ignoruje ostrzeżenia kompilatora.
Mike Ellery

10
Nie zgadzam się z powyższym stwierdzeniem, że liczy się spójność. Nie ma to nic wspólnego z konsekwencją; i tak nie jest to dobry rodzaj. Jest to obszar, w którym powinniśmy pozwolić programistom wyrazić swój własny styl. Jeśli jest osoba, która nie rozumie od razu żadnej z form, powinna od razu przestać czytać kod i rozważyć zmianę kariery. Powiem więcej: jeśli istnieje spójność kosmetyczna, której nie możesz automatycznie wymusić za pomocą skryptu, usuń ją. Koszt osuszenia ludzkiej moralności jest DUŻO ważniejszy niż te głupie decyzje, które ludzie podejmują na spotkaniu.
wilhelmtell

Odpowiedzi:


203

Z mojego doświadczenia wynika, że preferowane są testy formy if (ptr)lub if (!ptr). Nie zależą od definicji symbolu NULL. Nie stwarzają okazji do przypadkowego zadania. Są jasne i zwięzłe.

Edycja: jak SoapBox wskazuje w komentarzu, są one kompatybilne z klasami C ++, takimi jak auto_ptrobiekty, które działają jako wskaźniki i które zapewniają konwersję, boolaby włączyć dokładnie ten idiom. W przypadku tych obiektów jawne porównanie NULLmusiałoby wywołać konwersję do wskaźnika, co może mieć inne semantyczne skutki uboczne lub być droższe niż proste sprawdzenie istnienia, które boolimplikuje konwersja.

Preferuję kod, który mówi, co to znaczy, bez zbędnego tekstu. if (ptr != NULL)ma to samo znaczenie, if (ptr)ale kosztem zbędnej specyfiki. Następną logiczną rzeczą jest pisanie if ((ptr != NULL) == TRUE)i na tym polega szaleństwo. Język C jest jasne, że logiczna przetestowany przez if, whilelub jak ma szczególne znaczenie z niezerową wartością jest prawda i zero jest fałszywe. Nadmiarowość nie czyni tego jaśniejszym.


23
Ponadto są one kompatybilne z klasami otoki wskaźnika (shared_ptr, auto_ptr, scoped_tr itp.), Które zwykle zastępują operator bool (lub safe_bool).
SoapBox

2
Głosuję to jako „odpowiedź” po prostu ze względu na odniesienie do dodawania auto_ptr do dyskusji. Po napisaniu pytania staje się jasne, że jest to w rzeczywistości bardziej subiektywne niż cokolwiek innego i prawdopodobnie nie nadaje się do „odpowiedzi” przepełnienia stosu, ponieważ każda z nich może być „poprawna” w zależności od okoliczności.
Bryan Marble

2
@Bryan, szanuję to i jeśli pojawi się „lepsza” odpowiedź, możesz przesunąć znacznik wyboru w razie potrzeby. Jeśli chodzi o podmiotowość, tak, jest subiektywna. Jest to jednak przynajmniej dyskusja subiektywna, w której istnieją obiektywne kwestie, które nie zawsze są oczywiste, gdy stawia się pytanie. Linia jest zamazana. I subiektywne ... ;-)
RBerteig

1
Jedna ważna rzecz do zapamiętania: do niedawna opowiadałem się za zapisem „if (ptr)” zamiast „if (ptr! = NULL)” lub „if (ptr! = Nullptr)”, ponieważ jest bardziej zwięzłe, więc kod bardziej czytelny. Ale właśnie dowiedziałem się, że porównanie wywołuje (odpowiednio) ostrzeżenie / błąd przy porównaniu z NULL / nullptr w najnowszych kompilatorach. Więc jeśli chcesz sprawdzić wskaźnik, lepiej zrobić "if (ptr! = NULL)" zamiast "if (ptr)". Jeśli omyłkowo zadeklarujesz ptr jako int, pierwsze sprawdzenie spowoduje ostrzeżenie / błąd, podczas gdy drugie zostanie skompilowane bez żadnego ostrzeżenia.
Arnaud,

1
@David 天宇 Wong Nie, nie o to chodziło. Przykładem na tej stronie jest sekwencja dwóch wierszy, w int y = *x; if (!x) {...}której optymalizator może stwierdzić, że skoro *xbyłoby niezdefiniowane, jeśli xma wartość NULL, nie może mieć wartości NULL, a zatem !xmusi mieć wartość FALSE. Wyrażenie nie!ptr jest niezdefiniowanym zachowaniem. W tym przykładzie, jeśli test został wykonany przed jakimkolwiek użyciem , optymalizator pomyliłby się, eliminując test. *x
RBerteig


25

Zacznę od tego: spójność jest królem, decyzja jest mniej ważna niż spójność w bazie kodu.

W C ++

NULL jest definiowane jako 0 lub 0L w C ++.

Jeśli przeczytałeś Język programowania C ++ Bjarne Stroustrup sugeruje użycie 0jawnego unikania NULLmakra podczas wykonywania przypisań , nie jestem pewien, czy zrobił to samo z porównaniami, minęło trochę czasu, odkąd przeczytałem książkę, myślę, że właśnie to zrobiłif(some_ptr) bez wyraźnego porównania, ale jestem niejasny.

Powodem tego jest to, że NULLmakro jest zwodnicze (jak prawie wszystkie makra), w rzeczywistości jest 0dosłowne, a nie jest unikalnym typem, jak sugeruje nazwa. Unikanie makr to jedna z ogólnych wskazówek w C ++. Z drugiej strony 0wygląda jak liczba całkowita i nie jest porównywana lub przypisywana do wskaźników. Osobiście mógłbym pójść w obie strony, ale zazwyczaj pomijam wyraźne porównanie (chociaż niektórym to się nie podoba, dlatego prawdopodobnie masz współpracownika sugerującego zmianę).

Niezależnie od osobistych odczuć jest to w dużej mierze wybór najmniejszego zła, ponieważ nie ma jednej właściwej metody.

To jest klarowny i powszechny idiom i wolę taki, nie ma szans przypadkowego przypisania wartości podczas porównania i czyta się wyraźnie:

if(some_ptr){;}

Jest to jasne, jeśli wiesz, że some_ptrjest to typ wskaźnika, ale może również wyglądać jak porównanie liczb całkowitych:

if(some_ptr != 0){;}

Jest to jasne, w typowych przypadkach ma to sens ... Ale jest to nieszczelna abstrakcja, w NULLrzeczywistości jest 0dosłowna i może łatwo zostać niewłaściwie wykorzystana:

if(some_ptr != NULL){;}

C ++ 0x ma nullptr, który jest teraz preferowaną metodą, ponieważ jest wyraźny i dokładny, po prostu uważaj na przypadkowe przypisanie:

if(some_ptr != nullptr){;}

Dopóki nie będziesz w stanie przejść na C ++ 0x, uważałbym, że to strata czasu na martwienie się o to, której z tych metod używasz, wszystkie są niewystarczające, dlatego wynaleziono nullptr (razem z ogólnymi problemami programistycznymi, które doprowadziły do ​​idealnego przekazywania .) Najważniejsze jest zachowanie spójności.

W C

C to inna bestia.

W C NULL można zdefiniować jako 0 lub jako ((void *) 0), C99 pozwala na implementację zdefiniowanych zerowych stałych wskaźnika. Tak więc sprowadza się to do definicji NULL zdefiniowanej przez implementację i będziesz musiał sprawdzić to w swojej standardowej bibliotece.

Makra są bardzo powszechne i na ogół są często używane, aby nadrobić braki w ogólnej obsłudze programowania w języku i nie tylko. Język jest znacznie prostszy i bardziej powszechne jest poleganie na preprocesorze.

Z tego punktu widzenia prawdopodobnie zalecałbym użycie NULLdefinicji makra w C.


tl; dr i chociaż masz rację 0w C ++, to ma sens w katalogu C: (void *)0. 0jest lepsze niż NULLw C ++, ponieważ błędy typu mogą być PITA.
Matt Joiner,

@Matt: Ale i NULLtak wynosi zero.
GManNickG,

@GMan: Nie w moim systemie: #define NULL ((void *)0)odlinux/stddef.h
Matt Joiner

@Matt: w C ++. Powiedziałeś, że 0 jest lepsze, ale NULLmusi wynosić zero.
GManNickG,

To dobra uwaga, zapomniałem o tym wspomnieć i sugerując, poprawiam swoją odpowiedź. ANSI C może mieć NULL zdefiniowane jako ((void *) 0), C ++ definiuje NULL jako 0. Nie przeszukałem standardu dla tego bezpośrednio, ale rozumiem, że w C ++ NULL może wynosić 0 lub 0L.
M2tM

19

Używam if (ptr), ale zupełnie nie warto się o to spierać.

Podoba mi się mój sposób, ponieważ jest zwięzły, chociaż inni mówią, == NULLże jest łatwiejszy do odczytania i bardziej wyraźny. Widzę, skąd pochodzą, po prostu nie zgadzam się, że dodatkowe rzeczy sprawiają, że jest to łatwiejsze. (Nienawidzę makra, więc jestem stronniczy.) Do ciebie.

Nie zgadzam się z twoim argumentem. Jeśli nie otrzymujesz ostrzeżeń dla przydziałów warunkowych, musisz zwiększyć poziomy ostrzeżeń. Proste. (I z miłości do wszystkiego, co dobre, nie zmieniaj ich).

Zauważ, że w C ++ 0x możemy zrobić if (ptr == nullptr), co dla mnie jest przyjemniejsze. (Ponownie, nienawidzę makra. Ale nullptrjest fajne.) Jednak nadal to robię if (ptr), tylko dlatego, że jestem do tego przyzwyczajony.


mam nadzieję, że dodatkowe 5 lat doświadczenia zmieniło twoje zdanie :) Jeśli C ++ / C miałby operator typu if_exist (), to miałoby to sens.
magulla

10

Szczerze mówiąc, nie rozumiem, dlaczego to ma znaczenie. Każdy z nich jest całkiem jasny i każdy średnio doświadczony w C lub C ++ powinien rozumieć oba. Jednak jeden komentarz:

Jeśli planujesz rozpoznać błąd i nie kontynuować wykonywania funkcji (tj. Zamierzasz natychmiast zgłosić wyjątek lub zwrócić kod błędu), powinieneś uczynić z tego klauzulę ochronną:

int f(void* p)
{
    if (!p) { return -1; }

    // p is not null
    return 0;
}

W ten sposób unikniesz „kodu strzałki”.


ktoś wskazał tutaj kukuruku.co/hub/programming/i-do-not-know-c , if(!p)czyli niezdefiniowane zachowanie. To może zadziałać lub nie.
David 天宇 Wong

@David 天宇 Wong, jeśli mówisz o przykładzie nr 2 pod tym linkiem, if(!p)to nie jest niezdefiniowane zachowanie, jest to wiersz przed nim. I if(p==NULL)miałby dokładnie ten sam problem.
Mark Okup

8

Osobiście zawsze używałem, if (ptr == NULL)ponieważ jasno określa to mój zamiar, ale w tym momencie to tylko nawyk.

Użycie =zamiast ==will zostanie przechwycone przez dowolny kompetentny kompilator z poprawnymi ustawieniami ostrzeżeń.

Ważne jest, aby wybrać spójny styl dla swojej grupy i trzymać się go. Bez względu na to, którą drogą pójdziesz, w końcu przyzwyczaisz się do tego, a utrata tarcia podczas pracy z kodem innych osób będzie mile widziana.


7

Jeszcze jeden punkt na korzyść foo == NULLpraktyki: jeśli foojest, powiedzmy, a int *lub a bool *, to if (foo)czek może zostać przypadkowo zinterpretowany przez czytelnika jako testowanie wartości pointee, tj if (*foo). Jako . NULLPorównanie tutaj jest przypomnieniem, że mówimy o wskaźnik.

Ale przypuszczam, że dobra konwencja nazewnictwa sprawia, że ​​ten argument jest dyskusyjny.


4

Właściwie używam obu wariantów.

Są sytuacje, w których najpierw sprawdzasz poprawność wskaźnika, a jeśli ma on wartość NULL, po prostu wracasz / wychodzisz z funkcji. (Wiem, że może to prowadzić do dyskusji „jeśli funkcja ma tylko jeden punkt wyjścia”)

W większości przypadków sprawdzasz wskaźnik, robisz, co chcesz, a następnie rozwiązujesz przypadek błędu. Rezultatem może być brzydki kod z wcięciem x razy z wieloma ifami.


1
Osobiście nienawidzę kodu strzałki, który powodowałby użycie jednego punktu wyjścia. Dlaczego nie wyjść, skoro już znam odpowiedź?
ruslik

1
@ruslik: w C (lub C ++ w stylu klas C ++), wielokrotne zwracanie sprawia, że ​​czyszczenie jest trudniejsze. W „prawdziwym” C ++ to oczywiście nie problem, ponieważ czyszczenie jest obsługiwane przez RAII, ale niektórzy programiści (z mniej lub bardziej ważnych powodów) utknęli w przeszłości i albo odmawiają, albo nie mogą polegać na RAII .
jalf

@jalf w takich przypadkach gotomoże być bardzo przydatne. Również w wielu przypadkach, malloc()który wymagałby czyszczenia, można zastąpić szybszym alloca(). Wiem, że nie są zalecane, ale istnieją z jakiegoś powodu (według mnie dobrze nazwana etykieta gotojest znacznie czystsza niż zaciemnione if/elsedrzewo).
ruslik

1
allocajest niestandardowy i całkowicie niebezpieczny. Jeśli to się nie powiedzie, Twój program po prostu wybuchnie. Nie ma szans na odzyskanie, a ponieważ stos jest stosunkowo mały, prawdopodobne jest niepowodzenie. W przypadku niepowodzenia możliwe jest, że przebijesz stertę i wprowadzisz luki w zabezpieczeniach naruszające uprawnienia. Nigdy nie używaj allocalub vlas, chyba że masz małe ograniczenie rozmiaru, który zostanie przydzielony (a wtedy równie dobrze możesz po prostu użyć normalnej tablicy).
R .. GitHub PRZESTAŃ POMÓC LODOM

4

Język programowania C (K&R) wymagałby sprawdzenia null == ptr, aby uniknąć przypadkowego przypisania.


23
K&R zapytałby również „co to jest inteligentny wskaźnik?” W świecie C ++ sytuacja się zmienia.
Alex Emelianov

2
a raczej coś się zmieniło już w świecie C ++ ";)
jalf

3
Gdzie K&R to stwierdza (ref)? Sami nigdy nie używają tego stylu. W rozdziale 2 K & R2 nawet zalecają if (!valid)powyżej if (valid == 0).
schot

6
Uspokójcie się, ludzie. Powiedział k & r, a nie K&R. Są oczywiście mniej znane, ponieważ ich inicjały nie są nawet pisane wielkimi literami.
jmucchiello

1
kapitalizowanie tylko cię spowalnia
Derek

3

Jeśli styl i format będą częścią twoich recenzji, powinien istnieć uzgodniony przewodnik po stylu, z którym można się zmierzyć. Jeśli istnieje, zrób to, co mówi przewodnik stylu. Jeśli go nie ma, takie szczegóły należy pozostawić tak, jak są zapisane. To strata czasu i energii i odwraca uwagę od tego, co naprawdę powinny być odkrywane recenzje kodu. Poważnie, bez przewodnika po stylu nalegałbym z zasady, aby NIE zmieniać takiego kodu, nawet jeśli nie używa on preferowanej przeze mnie konwencji.

Nie żeby to miało znaczenie, ale moje osobiste preferencje są takie if (ptr). Znaczenie jest dla mnie bardziej oczywiste niż nawet if (ptr == NULL).

Może próbuje powiedzieć, że lepiej poradzić sobie z warunkami błędu, niż szczęśliwą ścieżką? W takim razie nadal nie zgadzam się z recenzentem. Nie wiem, czy istnieje na to przyjęta konwencja, ale moim zdaniem najbardziej „normalny” stan powinien mieć pierwszeństwo w każdym stwierdzeniu if. W ten sposób mam mniej do zrobienia, aby dowiedzieć się, o co chodzi w tej funkcji i jak działa.

Wyjątkiem jest sytuacja, gdy błąd powoduje wycofanie się z funkcji lub przywrócenie jej przed przejściem dalej. W takich przypadkach najpierw zajmuję się błędem:

if (error_condition)
  bail_or_fix();
  return if not fixed;

// If I'm still here, I'm on the happy path

Zajmując się z góry nietypowym stanem, mogę się tym zająć, a potem o nim zapomnieć. Ale jeśli nie mogę wrócić na szczęśliwą ścieżkę, zajmując się tym z przodu, należy to załatwić później głównej sprawie, ponieważ czyni to kod bardziej zrozumiałym. W mojej opinii.

Ale jeśli nie jest to przewodnik po stylu, to tylko moja opinia, a Twoja opinia jest równie ważna. Albo standaryzuj, albo nie. Nie pozwól recenzentowi pseudo-standaryzacji tylko dlatego, że ma opinię.


1

Jest to jedna z podstaw obu języków, w której wskaźniki oceniają typ i wartość, które mogą być używane jako wyrażenie sterujące, boolw C ++ i intC. Po prostu użyj tego.


1
  • Wskaźniki nie są wartościami logicznymi
  • Nowoczesne kompilatory C / C ++ emitują ostrzeżenie, gdy piszesz if (foo = bar)przez przypadek.

Dlatego wolę

if (foo == NULL)
{
    // null case
}
else
{
    // non null case
}

lub

if (foo != NULL)
{
    // non null case
}
else
{
    // null case
}

Jednak gdybym pisał zestaw wskazówek dotyczących stylu, nie umieszczałbym w nim takich rzeczy, jak:

Upewnij się, że wykonałeś zerową kontrolę wskaźnika.


2
To prawda, że ​​wskaźniki nie są wartościami logicznymi, ale w języku C instrukcje if nie przyjmują wartości logicznych: przyjmują wyrażenia całkowite.
Ken

@Ken: to dlatego, że C jest pod tym względem zepsuty. Koncepcyjnie jest to wyrażenie boolowskie i (moim zdaniem) powinno być tak traktowane.
JeremyP

1
Niektóre języki mają instrukcje if, które sprawdzają tylko wartość null / not-null. Niektóre języki mają instrukcje if, które testują tylko liczby całkowite dla znaku (test trójstronny). Nie widzę powodu, by uważać C za „zepsutego”, ponieważ wybrali inną koncepcję, którą lubisz. Jest wiele rzeczy, których nienawidzę w C, ale to po prostu oznacza, że ​​model programu C nie jest tym samym, co mój model mentalny, a nie to, że któryś z nas (ja lub C) jest uszkodzony.
Ken

@Ken: Wartość logiczna nie jest liczbą ani wskaźnikiem koncepcyjnie , nieważne w jakim języku.
JeremyP

1
Nie powiedziałem, że boolean to liczba lub wskaźnik. Powiedziałem, że nie ma powodu, aby nalegać, aby instrukcja „jeśli” miała lub może przyjmować tylko wyrażenie logiczne, i zaproponowałem kontrprzykłady oprócz tego, o którym mowa. Wiele języków wymaga czegoś innego niż wartość logiczna, a nie tylko w języku C „zero / niezerowe”. W informatyce posiadanie instrukcji if, która akceptuje (tylko) wartość logiczną, jest stosunkowo nowym rozwiązaniem.
Ken,

1

Jestem wielkim fanem tego, że C / C ++ nie sprawdza typy w logicznych warunków if, fororaz whilesprawozdania. Zawsze używam:

if (ptr)

if (!ptr)

nawet na liczbach całkowitych lub innych typach, które konwertują na bool:

while(i--)
{
    // Something to do i times
}

while(cin >> a >> b)
{
    // Do something while you've input
}

Kodowanie w tym stylu jest dla mnie bardziej czytelne i jaśniejsze. Tylko moja osobista opinia.

Ostatnio pracując nad mikrokontrolerem OKI 431 zauważyłem, że:

unsigned char chx;

if (chx) // ...

jest bardziej wydajny niż

if (chx == 1) // ...

ponieważ w późniejszym przypadku kompilator musi porównać wartość chx do 1. Gdzie chx jest po prostu flagą true / false.


1

Większość kompilatorów, z których korzystałem, przynajmniej ostrzega przed ifprzypisaniem bez dalszego poprawiania składni, więc nie kupuję tego argumentu. To powiedziawszy, użyłem obu profesjonalnie i nie preferuję żadnego. Jest == NULLto zdecydowanie jaśniejsze moim zdaniem.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.