În știința calculatoarelor, un pointer null sau o referință null[a] este o valoare salvată pentru a indica faptul că pointerul⁠(d) sau referința⁠(d) nu se referă la un obiect valid. Programele folosesc în mod obișnuit valoarea null pentru a reprezenta condiții precum sfârșitul unei liste⁠(d) de lungime necunoscută sau incapacitatea de a efectua o acțiune; această utilizare a indicatorilor null poate fi comparată cu tipurile nullabile⁠(d) și cu valoarea Nimic pentru un tip opțiune⁠(d).

Un pointer null nu trebuie confundat cu un pointer neinițializat⁠(d): un pointer null garantează faptul că nu este egal la comparație cu niciun pointer care indică către un obiect valid. Cu toate acestea, în funcție de limbaj și de implementare, un pointer neinițializat poate să nu dea o astfel de garanție. Acesta ar putea să fie egal la comparație cu alți pointeri valizi; sau ar putea fi egal la comparație cu pointerii null; și s-ar putea comporta chiar în ambele feluri la momente diferite; ori comparația ar putea avea comportament nedefinit⁠(d).

Deoarece un pointer null nu indică un obiect cu vreo semnificație, o încercare de a accesa datele stocate în acea locație de memorie (invalidă) poate provoca o eroare de rulare sau o blocare imediată a programului. Aceasta este eroarea de null pointer. Este unul dintre cele mai comune tipuri de slăbiciuni de software,[1] iar Tony Hoare, care a introdus conceptul, l-a denumit „o greșeală de un miliard de dolari”.

În C, se garantează că doi pointeri null de orice tip sunt egali.[2] Macroul de preprocesare NULL este definit ca o constantă de tip pointer definită la implementare în <stdlib.h⁠(d)>,[3] care în C99⁠(d) poate fi exprimată într-o manieră portabilă ca ((void *)0), valoarea întreagă 0 convertită⁠(d) la tipul void*.[4] Standardul C nu spune că pointerul null ar fi același cu indicatorul către adresa de memorie⁠(d) 0, deși în practică se poate întâmpla. Dereferențierea⁠(d) unui pointer null este comportament nedefinit⁠(d) în C,[5] și unei implementări conforme i se permite să presupună că orice pointer care este dereferențiat nu este null.

În practică, dereferențiarea unui pointer null poate duce la o încercare de citire sau scriere dintr-o zonă de memorie care nu este mapată, declanșând o eroare de segmentare⁠(d) sau o încălcare a regulilor de acces la memorie în sistemul de operare. Acest lucru se poate manifesta prin oprirea cu eroare a programului sau poate fi transformată într-o excepție⁠(d) software care poate fi prinsă de codul programului. Există, totuși, anumite circumstanțe în care nu este cazul. De exemplu, în modul real⁠(d) la procesoare x86, adresa 0000:0000 este citibilă și, de obicei, poate fi scrisă, iar dereferențierea unui pointer către acea adresă este o acțiune perfect validă, dar de obicei nedorită, care poate duce la un comportament nedefinit, dar care nu se blochează în aplicație. Există ocazii când dereferențiarea pointerului către adresa zero este intenționată și bine definită; de exemplu, codul BIOS scris în C pentru dispozitivele x86 în mod real pe 16 biți poate suprascrie tabela de descriptori de întrerupere⁠(d) (IDT) la adresa fizică 0 al mașinii prin dereferențierea unui pointer null pentru scriere. De asemenea, compilatorul ar putea și să optimizeze dereferențierea pointerului null, evitând eroarea de segmentare, dar provocând un alt comportament nedorit.[6]

Dereferențiere de null

modificare

În C++, macro-ul NULL a fost moștenit de la C, dar literalul întreg pentru zero a fost în mod tradițional preferat pentru a reprezenta o constantă de indicator null.[7] Cu toate acestea, C++11⁠(d) a introdus constanta explicită de pointer null nullptr⁠(d) și tipul nullptr_t⁠(d) pentru a fi folosit în schimb.

Alte limbaje

modificare

În unele medii de limbaje de programare (cel puțin o implementare proprietară Lisp, de exemplu), valoarea folosită ca indicator de null (denumit nil în Lisp ) poate fi de fapt un pointer către un bloc de date interne utile pentru implementare (dar nu accesibil în mod explicit din programele utilizatorului), permițând astfel folosirea aceluiași registru ca o constantă utilă și o modalitate rapidă de accesare a elementelor interne de implementare. Acesta este cunoscut ca vectorul nil.

În limbile cu o arhitectură pe etichete⁠(d), un pointer posibil null poate fi înlocuit cu o uniune etichetată care impune gestionarea explicită a cazului excepțional; de fapt, un pointer posibil null poate fi văzut ca un pointer etichetat cu o etichetă calculată.

Limbajele de programare folosesc literale diferite pentru indicatorul null. În Python, de exemplu, o valoare nulă se numește None. În Pascal și Swift, un indicator nul se numește nil. În Eiffel, se numește referință void.

Dereferențierea nullului

modificare

Deoarece un pointer null nu indică adresa unui obiect semnificativ, o încercare de dereferențiere (adică accesarea datelor stocate în acea locație de memorie) un pointer null provoacă de obicei (dar nu întotdeauna) o eroare de rulare sau o blocare imediată a programului. MITRE⁠(d) enumeră eroarea de pointer null drept una dintre cele mai frecvent exploatate slăbiciuni ale software-ului.[8]

  • In C, dereferențierea unui pointer null este comportament nedefinit. Multe implementări fac ca execuția unui astfel de cod să fie oprită cu încălcare a regulilor de acces, deoarece reprezentarea pointerului null este aleasă în așa fel încât să fie o adresă care nu este niciodată alocată de sistem pentru stocarea de obiecte. Acest comportament nu este însă universal. Nici nu este garantat, deoarece compilatoarelor li se permite să optimizeze programele sub presupunerea că nu au comportamente nedefinite.
  • În Delphi și în multe alte implementări de Pascal, constanta nil reprezintă un pointer null către prima adresă de memorie care este utilizată și pentru a inițializa variabile valide. Dereferențierea ridică o excepție externă a sistemului de operare care este mapată pe o instanță de excepție Pascal EAccessViolation dacă unitatea System.SysUtils este legată de clauza uses.
  • În Java, accesul la o referință null cauzează un NullPointerException (NPE), care poate fi prinsă de codul care tratează erori, dar practica preferată este de a asigura că asemenea excepții nu au loc niciodată.
  • În Lisp, nil este un obiect de clasa I. Prin convenție, (first nil) este nil, la fel ca (rest nil). Astfel că dereferențierea lui nil în aceste contexte nu va cauza o eroare, dar codul prost scris poate intra în buclă infinită.
  • În .NET, accesul la referințele null cauzează aruncarea unei NullReferenceException. Deși prinderea acestora este în general considerată o rea practică, acest tip de excepție poate fi prins și tratat de program.
  • În Objective-C⁠(d), se pot trimite mesaje la un obiect nil (care este un pointer null) fără a cauza întreruperea programului; mesajul este pur și simplu ignorat, și valoarea returnată (dacă există) este nil sau 0, în funcție de tip.
  • Până la introducerea Supervisor Mode Access Prevention (SMAP), o dereferențiere de pointer null putea fi exploatată prin maparea paginii zero în spațiul de adresă al atacatorului și deci transformarea oricărui pointer null într-un pointer care trimite la acea regiune, ceea ce în unele cazuri putea duce la execuție de cod arbitrar.

Atenuarea efectelor

modificare

Există tehnici pentru a facilita depanarea dereferințelor de pointer null.[9] Bond et al.[9] sugerează modificarea mașinii virtuale Java (JVM) pentru a ține evidența propagării nullurilor.

Limbajele funcționale pure și codul de utilizator rulat în multe limbaje interpretate sau de mașină virtuală nu suferă problema dereferențierii pointerului null, deoarece nu este oferit acces direct la pointeri și, în cazul limbajelor funcționale pure, tot codul și datele sunt imuabile.

În cazul în care un limbaj furnizează sau utilizează indicatori care altfel ar putea deveni null, este posibil să se atenueze sau să se evite dereferențierile de null în timpul executării prin furnizarea unor verificări la momentul compilării⁠(d) prin analiză statică⁠(d) sau alte tehnici, și este în plină desfășurare o tranziție către asistența sintactică din partea caracteristicilor limbajului, cum ar fi cele văzute în versiunile moderne ale limbajului de programare Eiffel⁠(d),[10] D,[11] și Rust.[12]

Analize similare pot fi efectuate folosind instrumente externe, în unele limbaje.

În 2009, Tony Hoare a declarat[13] că a inventat referința null în 1965 ca parte a limbajului ALGOL W.⁠(d) La data declarației, Hoare își descria invenția ca o „greșeală de un miliard de dolari”:

„Eu îi zic «greșeala mea de un miliard de dolari». A fost inventarea referinței null în 1965. Pe vremea aceea, proiectam primul sistem cuprinzător pentru referințe într-un limbaj orientat obiect (ALGOL W). Obiectivul meu era să mă asigur că orice utilizare a referințelor este absolut sigură, cu verificări efectuate automat de compilator. Dar nu am rezistat tentației de a introduce o referință null, pur și simplu pentru că era așa ușor de implementat. Aceasta a condus la nenumărate erori, vulnerabilități și defectări de sisteme, care probabil au cauzat daune și eforturi de un miliard de dolari în ultimii patruzeci de ani.”

Note de completare

modificare
  1. ^ Se folosește termenul null ca fiind cel mai răspândit, datorită utilizării lui în cele mai răspândite limbaje (C și cele derivate din el, inclusiv Java și ECMAScript). Pentru alte denumiri, vezi secțiunea #Alte limbaje.

Note bibliografice

modificare
  1. ^ „CWE-476: NULL Pointer Dereference”. MITRE. 
  2. ^ ISO/IEC 9899, clause 6.3.2.3, paragraph 4.
  3. ^ ISO/IEC 9899, clause 7.17, paragraph 3: NULL... which expands to an implementation-defined null pointer constant...
  4. ^ ISO/IEC 9899, clause 6.3.2.3, paragraph 3.
  5. ^ ISO/IEC 9899, clause 6.5.3.2, paragraph 4, esp. footnote 87.
  6. ^ Lattner, Chris (). „What Every C Programmer Should Know About Undefined Behavior #1/3”. blog.llvm.org (în engleză). Arhivat din original la . Accesat în . 
  7. ^ Stroustrup, Bjarne (martie 2001). „Chapter 5:
    The const qualifier (§5.4) prevents accidental redefinition of NULL and ensures that NULL can be used where a constant is required.”. The C++ Programming Language⁠(d) (ed. 14th printing of 3rd). United States and Canada: Addison–Wesley. p. 88. ISBN 0-201-88954-4.
     
  8. ^ „CWE-476: NULL Pointer Dereference”. MITRE. 
  9. ^ a b Bond, Michael D.; Nethercote, Nicholas; Kent, Stephen W.; Guyer, Samuel Z.; McKinley, Kathryn S. (). „Tracking bad apples”. Proceedings of the 22nd annual ACM SIGPLAN conference on Object oriented programming systems and applications - OOPSLA '07. p. 405. doi:10.1145/1297027.1297057. ISBN 9781595937865. 
  10. ^ „Void-safety: Background, definition, and tools”. Accesat în . 
  11. ^ Bartosz Milewski. „SafeD – D Programming Language”. Accesat în . 
  12. ^ „Fearless Security: Memory Safety”. Arhivat din original la . Accesat în . 
  13. ^ Tony Hoare (). „Null References: The Billion Dollar Mistake”. InfoQ.com. 

Bibliografie

modificare
  • Joint Technical Committee ISO/IEC JTC 1, Subcommittee SC 22, Working Group WG 14 (). International Standard ISO/IEC 9899 (PDF) (Committee Draft).