NULL Pointer Dereference
Aşağıdaki örnek kod parçacığı, Git kaynak kodundan alınmıştır:
void mark_tree_uninteresting(struct tree *tree)
{
struct object *obj = &tree->object;
if (!tree)
return;
....
}
Görüleceği üzere, obj pointer değişkenine, tree pointer değişkeninin gösterdiği veri yapısındaki object alanının adresi atanmaktadır.
Buradaki gibi pointer değişkeninin gösterdiği adres üzerinde yapılan işlemler dereferencing kavramıyla adlandırılır.
Peki bir NULL pointer üzerinde dereferencing işlemi yaparsak ne olur?
Bu işlemin sonucu undefined behaviour olup, ne olacağı belirsizdir.
Bu mutlaka programın çökeceği anlamına gelmemektedir. Öyle olsa idi daha şanslı olurduk zira hata durumu kararlı bir şekilde oluşturulabilirdi.
İşlemin sonucu implementasyona bağlı olarak sabit de değildir, yanı bazı implementasyonlarda her zaman programın çöktüğü, bazılarında ise bir şekilde devam ettiği gibi bir durumdan da söz edemeyiz.
Undefined behaviour'dan kastımız, gerçekten de ne olacağını bilemiyor olduğumuzdur.
Programcıların bir bölümü yukarıdaki örnek kodun sorun yaratmayacağını savunmaktadır [http://www.viva64.com/en/b/0306/]
Gerçekten de bu hataya rağmen uygulamanın çalışmasına devam ettiği senaryolarla karşılaşabilirsiniz. valgrind gibi bir analiz aracı kullanmıyorsanız, uzun süre bu tarz hataların farkedilmeden kalması mümkündür.
Pointer dereferencing yapmadan önce, mutlaka NULL durumunu kontrol etmelisiniz. Yukarıdaki kod şu şekilde olmalıydı:
void mark_tree_uninteresting(struct tree *tree)
{
if (!tree)
return;
struct object *obj = &tree->object;
....
}
Linux işletim sisteminde, NULL pointer dereference işlemi, diğer işletim sistemleri ile kıyaslandığında genellikle daha kararlı bir şekilde uygulamanın SIGSEGV sinyaliyle (segmentation fault) sonlanmasına yol açar.
NULL değeri nümerik olarak 0'a eşittir. C standartlarına göre NULL == 0
şeklindeki bir kontrol her zaman true
olması gerekir. Bununla birlikte NULL değerinin internal representation'ı farklı olabilir.
Linux altında NULL değerinin internal representation'ı da nümerik 0 şeklindedir. Bu tarz hataların Linux altında daha kararlı bir şekilde segfault hatası almasının altında, aşağıdaki nedenler yatar:
0x0
sanal adresi her yeni sürecin (process) adres uzayına, erişimine kısıt konularak haritalanır.- Bu şekilde
0x0
adresine (NULL pointer durumunda gösterilecek olan yer aynı zamanda) başka bir haritalama (mapping) yapılması da en başından engellenmiş olur. - NULL pointer dereference işlemi gerçekleştiğinde, bellekte olmayan
0x0
sanal adresindeki page'in getirilmesi için page fault oluşturulur. - Page fault handler içerisinde bu adrese erişim izni olmadığı anlaşılır.
- Linux çekirdeği ilgili sürece SIGSEGV sinyalini gönderir. Bu sinyali alan süreç bir core dosyası oluşturur ve çöker.
Ancak Linux altında da kararlı bir şekilde segfault alınmasını önleyen senaryolar bulunmaktadır. LLVM proje blogunda yayınlanan bir örnek aşağıdaki gibidir:
void contains_null_check(int *P)
{
int dead = *P;
if (P == 0)
return;
*P = 4;
}
Her ne kadar ilk bakışta sorun yok gibi görünsede, bazı derleyici optimizasyonlarının bir yan etkisi olarak, P = 0
olsa dahi *P
dereference işleminin yapıldığı senaryolar bulunmaktadır.
Özetlemek gerekirse, pointer değerler, dereference işlemi öncesi NULL
kontrolünden mutlaka geçirilmelidir.