Ce înseamnă să faci o “ verificare nulă ” în C sau C ++?

Am învățat C ++ și îmi este greu să înțeleg nulul. În special, tutorialele pe care le-am citit menționează că fac o „verificare nulă”, dar nu sunt sigur ce înseamnă asta sau de ce este necesar.

  • Ce este exact nul?
  • Ce înseamnă să „verific null”?
  • Trebuie întotdeauna să verific null?

Orice exemple de cod ar fi foarte apreciate.

Comentarii

Răspuns

În C și C ++, indicatorii sunt în mod inerent nesiguri, adică atunci când renunțați la un pointer, este responsabilitatea dvs. să vă asigurați că indică undeva valabil; aceasta face parte din ceea ce înseamnă „gestionarea manuală a memoriei” (spre deosebire de schemele automate de gestionare a memoriei implementate în limbaje precum Java , PHP sau runtime-ul .NET, care nu vă va permite să creați referințe nevalide fără efort considerabil).

O soluție obișnuită care prinde multe erori este să setați toți indicatorii care nu indică nimic ca NULL (sau, în C ++ corect, 0) și verificarea pentru aceasta înainte de a accesa indicatorul. Mai exact, este o practică obișnuită să inițializați toate pointerele la NULL (cu excepția cazului în care aveți deja ceva la care să le indicați când le declarați) și să le setați la NULL când delete sau free() le (cu excepția cazului în care ies din domeniul de aplicare imediat după aceea). Exemplu (în C, dar și C ++ valid):

void fill_foo(int* foo) { *foo = 23; // this will crash and burn if foo is NULL } 

O versiune mai bună:

void fill_foo(int* foo) { if (!foo) { // this is the NULL check printf("This is wrong\n"); return; } *foo = 23; } 

Fără verificarea nulă, trecerea unui pointer NULL în această funcție va provoca un segfault și nu puteți face nimic – sistemul de operare vă va ucide pur și simplu procesul și poate arunca nucleul sau va afișa o casetă de dialog de raport de avarie. Odată cu verificarea nulă, puteți efectua o gestionare corectă a erorilor și vă puteți recupera cu grație – corectați singur problema, anulați operațiunea curentă, scrieți o intrare jurnal, informați utilizatorul, indiferent ce este adecvat.

Comentarii

  • @MrLister ce vrei să spui, controalele nule nu funcționează în C ++? ‘ Trebuie doar să inițializați indicatorul la nul atunci când îl declarați.
  • Ceea ce vreau să spun este că trebuie să vă amintiți să setați indicatorul la NULL sau a câștigat ‘ nu funcționează. Și dacă vă amintiți, cu alte cuvinte, dacă știți că indicatorul este NULL, ‘ nu veți avea nevoie să apelați fill_foo oricum. fill_foo verifică dacă indicatorul are o valoare, nu dacă indicatorul are o valoare validă . În C ++, indicatoarele nu sunt garantate să fie NULL sau să aibă o valoare validă.
  • Un assert () ar fi o soluție mai bună aici. ‘ nu are rost să încerce să ” să fie în siguranță „. Dacă NULL a fost transmis, ‘ este în mod evident greșit, așa că de ce să nu ne blocăm în mod explicit pentru a face programatorul complet conștient? (Și în producție, nu contează ‘, deoarece ‘ ați demonstrat că nimeni nu va numi fill_foo () cu NULL, nu? Într-adevăr, ‘ nu este atât de greu.)
  • Nu itați ‘ nu uitați să menționăm că o versiune și mai bună a acestei funcții ar trebui să utilizeze referințe în locul indicatoarelor, făcând verificarea NULL învechită.
  • Nu este vorba despre gestionarea manuală a memoriei și un program gestionat va exploda și el, ( sau ridicați cel puțin o excepție, la fel cum va face un program nativ în majoritatea limbilor), dacă încercați să dererați o referință nulă.

Răspundeți

Celelalte răspunsuri au acoperit destul de mult întrebarea dvs. exactă. O verificare nulă este făcută pentru a vă asigura că indicatorul pe care l-ați primit indică de fapt o instanță validă de un tip (obiecte, primitive etc.).

Voi adăuga propriul meu sfat aici, Evitați verificările nule. 🙂 Verificările nule (și alte forme de programare defensivă) aglomerează codul și, de fapt, îl fac mai predispus la erori decât alte tehnici de gestionare a erorilor.

Tehnica mea preferată atunci când vine vorba de indicatorii de obiect este de a utiliza modelul obiectului nul . Aceasta înseamnă returnarea unui (pointer – sau chiar mai bun, referință la o) matrice sau listă goală în loc de nul, sau returnând un șir gol („”) în loc de nul sau chiar șirul „0” (sau ceva echivalent cu „nimic” în context) unde vă așteptați să fie analizat la un întreg.

Ca bonus, aici „este un lucru puțin probabil pe care nu l-ați fi știut despre indicatorul nul, care a fost implementat (pentru prima dată în mod oficial) de CAR Hoare pentru limbajul Algol W în 1965.

O numesc greșeala mea de miliarde de dolari. A fost invenția referinței nule în 1965. În acel moment, proiectam primul sistem de tip cuprinzător pentru referințe într-un obiect orientat (ALGOL W). Scopul meu a fost să mă asigur că toată utilizarea referințelor ar trebui să fie absolut sigură, cu verificarea efectuată automat de compilator. Dar nu am putut rezista tentației de a introduce o referință nulă, pur și simplu pentru că a fost așa ușor de implementat. Acest lucru a dus la nenumărate erori, vulnerabilități și blocări ale sistemului, care au cauzat probabil un miliard de dolari de durere și daune în ultimii patruzeci de ani.

Comentarii

  • Obiectul Null este chiar mai rău decât să ai doar un indicator nul. Dacă un algoritm X necesită date Y pe care nu le aveți, atunci acesta este un bug în programul dvs. , pe care îl ascundeți pur și simplu pretinzând că dați.
  • Depinde de contextul și testarea în orice sens pentru ” prezența datelor ” bate testarea pentru nul în cartea mea. Din experiența mea, dacă un algoritm funcționează pe, să zicem, o listă, iar lista este goală, atunci algoritmul pur și simplu nu are nimic de făcut și realizează acest lucru folosind doar instrucțiuni de control standard, cum ar fi for / foreach.
  • Dacă algoritmul nu are nimic de făcut, atunci de ce îl suni chiar? Și motivul pentru care ați fi vrut să-l numiți în primul rând este deoarece face ceva important .
  • @DeadMG Deoarece programele sunt despre intrare și în lumea reală, spre deosebire de sarcini pentru teme, introducerea poate fi irelevantă (de exemplu, goală). Codul este încă apelat în ambele sensuri. Aveți două opțiuni: fie verificați relevanța (sau golul), fie vă proiectați algoritmii astfel încât să citească și să funcționeze bine fără a verifica în mod explicit relevanța folosind declarații condiționale.
  • Am venit aici pentru a face aproape același comentariu, așa că ți-am dat votul. Cu toate acestea, aș adăuga, de asemenea, că acest lucru este reprezentativ pentru o problemă mai mare a obiectelor zombie – oricând aveți obiecte cu inițializare (sau distrugere) în mai multe etape, care nu sunt pe deplin vii, dar nu chiar moarte. Când vedeți codul ” sigur ” în limbi fără finalizare deterministă, care a adăugat verificări în fiecare funcție pentru a vedea dacă obiectul a fost eliminat, această problemă generală este creșterea capului ‘. Nu ar trebui niciodată dacă-nul, ar trebui să lucrați cu stări care au obiectele de care au nevoie pentru viața lor.

Răspuns

Valoarea indicatorului nul reprezintă un „nicăieri” bine definit; este o valoare a indicatorului invalid care este garantată să compare inegal cu orice altă valoare a indicatorului. Încercarea de a dereferenta un indicator nul are ca rezultat un comportament nedefinit și, de obicei, va duce la o eroare în timpul rulării, așa că doriți să vă asigurați că un indicator nu este NULL înainte de a încerca să îl deranjați. Un număr de funcții de bibliotecă C și C ++ vor returna un pointer nul pentru a indica o condiție de eroare. De exemplu, funcția de bibliotecă malloc va returna o valoare nulă a indicatorului dacă nu poate aloca numărul de octeți solicitați și încercarea de a accesa memoria prin acel indicator va conduce (de obicei) la o eroare de execuție:

int *p = malloc(sizeof *p * N); p[0] = ...; // this will (usually) blow up if malloc returned NULL 

Deci, trebuie să ne asigurăm că apelul malloc a reușit verificând valoarea p împotriva NULL:

int *p = malloc(sizeof *p * N); if (p != NULL) // or just if (p) p[0] = ...; 

Acum, agățați-vă de șosete un minut, acest lucru va primi un puțin accidentat.

Există un pointer nul valoare și un pointer nul constant , iar cele două nu sunt neapărat aceleași. Indicatorul nul valoare este orice valoare pe care arhitectura subiacentă o folosește pentru a reprezenta „nicăieri”. Această valoare poate fi 0x00000000 sau 0xFFFFFFFF sau 0xDEADBEEF sau ceva complet diferit. Nu presupuneți că indicatorul nul valoare este întotdeauna 0.

Indicatorul nul constant , OTOH, este întotdeauna o expresie integrală cu valoare 0. În ceea ce privește codul sursă , 0 (sau orice expresie integrală care se evaluează la 0) reprezintă un indicator nul. Atât C cât și C ++ definesc macrocomanda NULL ca constantă a indicatorului nul. Când codul dvs. este compilat, indicatorul nul constant va fi înlocuit cu indicatorul valoare corespunzător în codul mașinii generate.

De asemenea, rețineți că NULL este doar una dintre multele valori posibile ale indicatorului invalid ; dacă declarați o variabilă de pointer automat fără a o inițializa în mod explicit, cum ar fi

int *p; 

valoarea stocată inițial în variabilă este nedeterminată , și poate să nu corespundă unei adrese de memorie valabile sau accesibile. Din păcate, nu există un mod (portabil) de a spune dacă o valoare a indicatorului non-NULL este validă sau nu înainte de a încerca să o utilizați. Deci, dacă aveți de-a face cu pointeri, este de obicei o idee bună să le inițializați în mod explicit la NULL când le declarați și să le setați la NULL atunci când nu indică în mod activ nimic.

Rețineți că aceasta este mai mult o problemă în C decât C ++; idiomatic C ++ nu ar trebui să folosească atât de mult indicii.

Răspuns

Există câteva metode, toate în esență fac la fel lucru.

 int *foo = NULL; //sometimes set to 0x00 or 0 or 0L instead of NULL 

verificare nulă (verificați dacă indicatorul este nul), versiunea A

 if( foo == NULL) 

verificare nulă, versiunea B

 if( !foo ) //since NULL is defined as 0, !foo will return a value from a null pointer 

verificare nulă, versiunea C

 if( foo == 0 ) 

Dintre cele trei, prefer să folosesc prima verificare, întrucât spune în mod explicit viitorilor dezvoltatori ceea ce încercați să verificați ȘI arată clar că v-ați așteptat ca foo să fie un indicator.

Răspunde

Tu nu. Singurul motiv pentru care se folosește un pointer în C ++ este că doriți în mod explicit prezența indicatorilor nul; altfel, puteți lua o referință, care este atât mai ușor de utilizat din punct de vedere semantic, cât și care garantează non-nulitate.

Comentarii

  • @James: ‘ nou ‘ în modul kernel?
  • @James: O implementare a C ++ care reprezintă capacitățile pe care o majoritate semnificativă a C ++ coderii se bucură. Aceasta include toate caracteristicile de limbaj C ++ 03 (cu excepția export) și toate caracteristicile bibliotecii C ++ 03 și TR1 și o bucată bună de C ++ 11.
  • Îmi doresc ca oamenii să nu ‘ să spună că ” referințele garantează non-nul. ” Nu au ‘ t. Este la fel de ușor să generezi o referință nulă ca un pointer nul și se propagă în același mod.
  • @Stargazer: Întrebarea este 100% redundantă atunci când folosești instrumentele așa cum sunt proiectanții de limbă. practica sugerează că ar trebui.
  • @DeadMG, nu contează ‘ dacă este redundant. Nu ‘ nu ați răspuns la întrebare . ‘ îl voi spune din nou: -1.

Răspuns

Dacă nu verificați valoarea NULL, în special, dacă acesta este un pointer către o structură, este posibil să întâlniți o vulnerabilitate de securitate – dereferere de pointer NULL. Dereferența de pointer NULL poate duce la alte vulnerabilități de securitate grave, cum ar fi depășirea bufferului, starea cursei … care poate permite atacatorului să preia controlul asupra computerului dvs.

Mulți furnizori de software precum Microsoft, Oracle, Adobe, Apple … lansează patch-uri software pentru a remedia aceste vulnerabilități de securitate. Cred că ar trebui să verificați valoarea NULL a fiecărui pointer 🙂

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *