Tidak semua pointer menunjuk ke objek yang valid. Kadang kita perlu menandai bahwa sebuah pointer “belum menunjuk ke mana-mana”—misalnya pointer yang baru dideklarasikan sebelum menerima alamat, atau pointer yang sudah dibebaskan dengan free. Untuk kebutuhan ini C menyediakan konstanta NULL yang merupakan null pointer—nilai pointer khusus yang dijamin tidak menunjuk ke region memori mana pun. Pada chapter ini kita akan memahami apa itu NULL, bagaimana cara memeriksanya sebelum dereference, dan apa yang terjadi (sebagaimana mestinya: crash) bila kita nekat dereference NULL.

Apa Itu NULL #

NULL didefinisikan di beberapa header standar (<stdio.h>, <stdlib.h>, <string.h>, dan lainnya) sebagai konstanta pointer yang nilainya dijamin membedakannya dari alamat memori valid mana pun. Standar C (ISO/IEC 9899 §6.3.2.3) menyatakan: jika konstanta integer 0 dikonversi ke tipe pointer, hasilnya adalah null pointer, dan konstanta ini dapat dibandingkan dengan tipe pointer apa pun. Karena itu, NULL biasanya didefinisikan sebagai ((void *)0) atau 0. Pada C23 diperkenalkan nullptr sebagai keyword khusus bertipe nullptr_t untuk menghilangkan ambiguitas tipe yang dimiliki NULL lama.

Mekanisme kerjanya: saat compiler melihat int *p = NULL;, compiler menyimpan nilai integer 0 (atau representasi null pointer yang sesuai arsitektur) ke dalam variabel p. Saat runtime, dereference pointer ini segera memicu segmentation fault pada sistem operasi modern karena alamat 0 selalu dipetakan sebagai guard page yang tidak boleh diakses. Inilah mengapa NULL lebih baik daripada membiarkan pointer tidak terinisialisasi: NULL paling tidak memberi kita “titik tangan” yang bisa diperiksa dan dijamin crash jika di-dereference, bukan perilaku acak.

Visualisasi Konsep #

Diagram berikut membandingkan tiga keadaan pointer: pointer valid yang menunjuk ke variabel, pointer NULL yang sengaja dikosongkan, dan pointer liar yang berisi alamat acak:

Pada diagram di atas, hanya pointer valid yang aman di-dereference: alamat p pasti menunjuk ke variabel data. Pointer NULL sengaja dikosongkan—pemeriksaan if (p == NULL) dapat mendeteksi kondisi ini sebelum dereference, dan jika tetap di-dereference akan memicu crash yang dapat didiagnosis. Pointer liar (pointer yang belum diinisialisasi) berisi alamat acak; perilaku dereference-nya tidak terprediksi dan bisa membaca atau menulis ke region memori mana pun, sering kali tanpa crash sehingga bug diam-diam merusak data. Aturan praktis: selalu inisialisasi pointer saat deklarasi, entah dengan alamat valid atau dengan NULL.

c
#include <stdio.h>

int main(void) {
    int data = 42;
    int *p = NULL;   /* inisialisasi eksplisit ke NULL */

    printf("Nilai pointer p (NULL): %p\n", (void*)p);

    /* Cek NULL sebelum dereference */
    if (p == NULL) {
        printf("p masih NULL, lewati dereference\n");
        p = &data;
    }

    /* Sekarang aman di-dereference */
    if (p != NULL) {
        printf("Setelah di-set ke &data: *p = %d\n", *p);
    }

    return 0;
}

Output yang Dihasilkan:

text
Nilai pointer p (NULL): (nil)
p masih NULL, lewati dereference
Setelah di-set ke &data: *p = 42

Cetak %p pada pointer NULL menghasilkan (nil) di glibc—bukan deretan heksadesimal seperti pointer biasa. Inilah tanda visual bahwa pointer sedang kosong. Cek if (p == NULL) (atau padanannya if (!p)) adalah guard wajib sebelum dereference pointer yang mungkin tidak terinisialisasi atau mungkin NULL karena dikembalikan dari fungsi yang bisa gagal.

Mari kita telaah baris-baris kode di atas:

  1. Baris 4 (int *p = NULL;): inisialisasi eksplisit pointer p ke NULL. Tanpa inisialisasi, p berisi sampah dan menjadi wild pointer—topik yang akan kita bahas di chapter khusus.
  2. Baris 6 (printf("... %p", (void*)p);): cetak nilai pointer NULL. Format %p menampilkan (nil) di glibc untuk NULL.
  3. Baris 9 (if (p == NULL)): perbandingan pointer dengan NULL. Bisa juga ditulis if (!p) karena NULL bernilai 0 (false dalam konteks boolean).
  4. Baris 10 (p = &data;): assign pointer ke alamat variabel data yang valid. Setelah baris ini, p tidak lagi NULL.
  5. Baris 14 (if (p != NULL)): cek ulang sebelum dereference. Pola defensif ini sangat penting untuk pointer yang menerima nilai dari fungsi eksternal atau dari input yang belum diverifikasi.

Fungsi yang Mengembalikan NULL #

Banyak fungsi standar C mengembalikan NULL untuk menandai kegagalan: malloc jika memori habis, fopen jika file tidak ditemukan, getenv jika variabel lingkungan tidak ada. Pola yang benar adalah memeriksa hasil fungsi sebelum dereference. Contoh berikut memperlihatkan fungsi cari_elemen yang mengembalikan pointer ke elemen yang cocok, atau NULL bila tidak ditemukan.

c
#include <stdio.h>
#include <stdlib.h>

/* Fungsi yang mungkin mengembalikan NULL */
int *cari_elemen(int *arr, int n, int target) {
    for (int i = 0; i < n; i++) {
        if (arr[i] == target) {
            return &arr[i];   /* ketemu, kembalikan alamat */
        }
    }
    return NULL;              /* tidak ketemu, kembalikan NULL */
}

int main(void) {
    int data[5] = {10, 20, 30, 40, 50};

    int *hasil1 = cari_elemen(data, 5, 30);
    int *hasil2 = cari_elemen(data, 5, 99);

    if (hasil1 != NULL) {
        printf("Ketemu 30 di indeks %ld, nilai = %d\n",
               hasil1 - data, *hasil1);
    } else {
        printf("30 tidak ketemu\n");
    }

    if (hasil2 != NULL) {
        printf("Ketemu 99 di indeks %ld\n", hasil2 - data);
    } else {
        printf("99 tidak ketemu (fungsi mengembalikan NULL)\n");
    }

    return 0;
}

Output yang Dihasilkan:

text
Ketemu 30 di indeks 2, nilai = 30
99 tidak ketemu (fungsi mengembalikan NULL)

Tanpa cek if (hasil != NULL), pemanggil yang menerima NULL bisa langsung dereference dan crash. Pola “return NULL on failure” ini begitu umum sehingga menjadi idiom standar di C—selalu periksa sebelum pakai.

Bahaya Dereference NULL #

Inilah yang terjadi bila kita mengabaikan pemeriksaan dan langsung dereference NULL:

c
#include <stdio.h>

int main(void) {
    int *p = NULL;
    printf("Mencoba dereference NULL...\n");
    printf("*p = %d\n", *p);   /* UB: dereference NULL */
    return 0;
}

Output yang Dihasilkan:

text
Mencoba dereference NULL...
Segmentation fault (core dumped)

Program crash dengan segmentation fault—sinyal SIGSEGV dengan exit code 139. Sistem operasi sengaja menjadikan halaman pertama virtual address space (termasuk alamat 0) sebagai guard page yang tidak bisa diakses, sehingga dereference NULL langsung memicu page fault dan kernel menghentikan program. Perilaku ini sebenarnya “baik” karena crash langsung terjadi dan mudah didiagnosis; jauh lebih buruk jika dereference pointer liar ke alamat acak yang kebetulan valid—bug seperti itu bisa diam-diam merusak data tanpa crash.