Cevap, bakış açınıza bağlıdır:
C ++ standardına göre karar verirseniz, önce tanımsız davranışı aldığınız için boş bir başvuru alamazsınız. Bu ilk tanımlanmamış davranış olayından sonra, standart her şeyin olmasına izin verir. Yani, yazarsanız *(int*)0
, dil standardı bakış açısından, bir boş göstericiye başvurmadan başvurduğunuz gibi zaten tanımlanmamış bir davranışınız olur. Programın geri kalanı ilgisizdir, bu ifade bir kez uygulandığında oyunun dışında kalırsınız.
Bununla birlikte, pratikte boş referanslar, boş işaretçilerden kolayca oluşturulabilir ve boş referansın arkasındaki değere gerçekten erişmeye çalışana kadar fark etmezsiniz. Örneğiniz biraz fazla basit olabilir, çünkü herhangi bir iyi optimizasyon derleyicisi tanımlanmamış davranışı görecek ve ona bağlı olan her şeyi basitçe optimize edecek (boş referans oluşturulmayacak, optimize edilecek).
Yine de, bu optimizasyon, derleyicinin tanımlanmamış davranışı kanıtlamasına bağlıdır, ki bunu yapmak mümkün olmayabilir. Bir dosyanın içindeki şu basit işlevi düşünün converter.cpp
:
int& toReference(int* pointer) {
return *pointer;
}
Derleyici bu işlevi gördüğünde, göstericinin boş gösterici olup olmadığını bilmez. Böylece herhangi bir işaretçiyi karşılık gelen referansa dönüştüren bir kod üretir. (Btw: İşaretçiler ve referanslar assembler'daki aynı canavar olduğundan bu bir noop'tur.) Şimdi, eğer user.cpp
kodu içeren başka bir dosyanız varsa
#include "converter.h"
void foo() {
int& nullRef = toReference(nullptr);
cout << nullRef; //crash happens here
}
derleyici toReference()
, aktarılan göstericiye başvurmayacağını bilmez ve pratikte boş bir başvuru olacak geçerli bir başvuru döndürdüğünü varsayar. Çağrı başarılı olur, ancak referansı kullanmaya çalıştığınızda program çöker. İnşallah. Standart, pembe fillerin görünümü dahil her şeyin olmasına izin verir.
Bunun neden alakalı olduğunu sorabilirsiniz, sonuçta, tanımlanmamış davranış zaten içeride tetiklendi toReference()
. Cevap, hata ayıklamaktır: Boş referanslar tıpkı boş işaretçilerin yaptığı gibi yayılabilir ve çoğalabilir. Boş referansların var olabileceğinin farkında değilseniz ve bunları oluşturmaktan kaçınmayı öğrenirseniz, üye işlevinizin neden sadece eski bir int
üyeyi okumaya çalışırken çöktüğünü anlamaya çalışmak için epey zaman harcayabilirsiniz (cevap: örnek üyenin çağrısında boş bir referans vardı, bu yüzden this
bir boş gösterici ve üyeniz adres 8 olarak hesaplanır).
Peki boş referansları kontrol etmeye ne dersiniz? Hattı sen verdin
if( & nullReference == 0 ) // null reference
sorunuzda. Pekala, bu işe yaramaz: Standarda göre, bir boş göstericiye başvurursanız tanımsız bir davranışınız olur ve bir boş göstericiye başvurmadan boş bir referans oluşturamazsınız, bu nedenle boş referanslar yalnızca tanımsız davranış alanı içinde bulunur. Derleyiciniz tanımsız davranışı tetiklemediğinizi varsayabileceğinden, boş referans diye bir şeyin olmadığını varsayabilir (boş referanslar üreten kodu kolayca yaysa bile!). Böyle olunca if()
durumu görür, bunun doğru olamayacağı sonucuna varır ve tüm if()
ifadeyi bir kenara atar . Bağlantı süresi optimizasyonlarının tanıtılmasıyla, boş referansları sağlam bir şekilde kontrol etmek imkansız hale geldi.
TL; DR:
Boş referanslar biraz korkunç bir varlıktır:
Varlıkları imkansız görünüyor (= standart olarak),
ancak varlar (= üretilen makine koduyla),
ancak var olup olmadıklarını göremezsiniz (= girişimleriniz ortadan kalkacak),
ancak yine de sizi farkında olmadan öldürebilirler (= programınız tuhaf noktalarda çöküyor veya daha kötüsü).
Tek umudunuz onların var olmamasıdır (= programınızı onları yaratmamak için yazın).
Umarım bu sana musallat olmaz!