Pada chapter sebelumnya kita melihat bahwa string di C hanyalah array char yang diakhiri byte bernilai 0. Byte 0 tersebut dikenal sebagai null terminator dan ditulis dalam kode C sebagai '\0'. Karakter ini bukan karakter biasa seperti spasi atau angka nol '0' (yang bernilai ASCII 48); null terminator adalah byte bernilai numerik 0 yang berperan sebagai sentinel penanda akhir string. Tanpa null terminator, semua fungsi string library—printf("%s"), strlen, strcpy, strcmp—akan terus membaca memori melewati batas array dan menimbulkan undefined behavior (UB). Pada chapter ini kita akan menelusuri bagaimana null terminator bekerja, apa yang terjadi jika buffer kehilangan null terminator, dan bagaimana menggunakannya secara eksplisit saat membangun string secara manual.
Null Terminator di Memori #
Mari kita lihat langsung bagaimana null terminator disimpan di dalam memori. Array char s[] = "ABCD" sebenarnya menempati 5 byte: empat karakter yang terlihat ditambah satu byte 0 di akhir.
Diagram berikut memperlihatkan layout memori string literal "ABCD" beserta null terminator implisit di akhir:
Pada diagram di atas, string literal "ABCD" menempati 5 byte berurutan di memori: empat byte pertama berisi karakter yang terlihat (A, B, C, D), dan byte kelima otomatis berisi '\0' (nilai ASCII 0) sebagai sentinel penanda akhir string. Karakter '\0' berbeda dengan karakter '0' (ASCII 48) dan karakter space ' ' (ASCII 32)—kebingungan ketiga karakter ini adalah sumber bug umum di C. Fungsi string library seperti strlen, printf("%s"), strcpy, dan strcmp mengandalkan null terminator untuk mengetahui kapan harus berhenti membaca; tanpa '\0', fungsi-fungsi ini akan terus membaca melewati batas array dan menimbulkan undefined behavior.
#include <stdio.h>
int main(void) {
/* Null terminator adalah karakter '\0' dengan nilai ASCII 0 */
char s[] = "ABCD"; /* sizeof s = 5 (4 karakter + '\0') */
printf("Isi memori per byte (termasuk null terminator):\n");
for (size_t i = 0; i < sizeof s; i++) {
printf(" s[%zu] = %c (ASCII %d)\n", i, s[i] ? s[i] : '0', s[i]);
}
return 0;
}Output yang Dihasilkan:
Isi memori per byte (termasuk null terminator):
s[0] = A (ASCII 65)
s[1] = B (ASCII 66)
s[2] = C (ASCII 67)
s[3] = D (ASCII 68)
s[4] = 0 (ASCII 0)Mari kita telaah baris-baris kode di atas:
- Baris 5 (
char s[] = "ABCD";): compiler mengalokasikan 5 byte di stack. Empat byte pertama berisi65,66,67,68(kode ASCIIA,B,C,D). Byte kelima otomatis diisi0(null terminator) oleh compiler karena initializer berupa string literal. - Baris 7 (
for (size_t i = 0; i < sizeof s; i++)): kita mengiterasi dari indeks0hinggasizeof s - 1. Karenasizeof sadalah5, perulangan berjalan untuki = 0, 1, 2, 3, 4. Indeks4adalah posisi null terminator. - Baris 8 (
printf(" s[%zu] = %c (ASCII %d)\n", i, s[i] ? s[i] : '0', s[i])): ekspresis[i] ? s[i] : '0'adalah trik untuk menampilkan0(sebagai karakter0) bila byte bernilai0—karenaprintf("%c", 0)mencetak karakter kontrol yang tidak terlihat di banyak terminal. Parameter kedua%dmencetak nilai numerik sebenarnya (0).
Bahaya Buffer Tanpa Null Terminator #
Bayangkan kita membangun array char secara manual tetapi lupa menyertakan null terminator. Berikut adalah demonstrasi bagaimana akses per byte tetap aman, namun fungsi string seperti printf("%s") menjadi berbahaya.
#include <stdio.h>
int main(void) {
char s[] = "Budi";
/* Cara benar: berhenti saat menemui '\0' */
int n = 0;
while (s[n] != '\0') {
n++;
}
printf("Panjang string (cara benar) = %d\n", n);
/* Buffer TANPA null terminator: jangan pakai %s */
char rusak[4];
rusak[0] = 'B';
rusak[1] = 'u';
rusak[2] = 'd';
rusak[3] = 'i';
/* Akses byte per byte tetap aman */
printf("Isi rusak[0..3] = %c %c %c %c\n",
rusak[0], rusak[1], rusak[2], rusak[3]);
return 0;
}Output yang Dihasilkan:
Panjang string (cara benar) = 4
Isi rusak[0..3] = B u d iBuffer rusak berisi 4 byte B, u, d, i tanpa null terminator. Mengaksesnya per byte dengan indeks (seperti rusak[0], rusak[1], dst.) tetap aman karena kita tahu persis indeks berapa yang valid. Akan tetapi, kode di atas sengaja tidak memanggil printf("%s", rusak) karena printf akan membaca rusak[0], rusak[1], rusak[2], rusak[3], lalu melanjutkan ke byte kelima, keenam, dan seterusnya—sampai kebetulan menemui byte 0 di tempat lain di memori. Hasilnya tidak terduga, bisa berupa karakter acak, segmentation fault, atau kebocoran data sensitif dari memori program.
String Kosong hanyalah Null Terminator #
String kosong "" adalah kasus khusus yang menarak: array hanya berisi satu byte yaitu null terminator. Hal ini menjelaskan mengapa strlen("") = 0 dan sizeof "" = 1.
#include <stdio.h>
/* Demonstrasi string kosong: hanya berisi null terminator */
int main(void) {
char kosong[] = "";
char berisi[] = "A";
printf("sizeof kosong = %zu\n", sizeof kosong);
printf("sizeof berisi = %zu\n", sizeof berisi);
printf("kosong[0] = %d\n", kosong[0]);
printf("berisi[0] = %c (ASCII %d)\n", berisi[0], berisi[0]);
printf("berisi[1] = %d (null terminator)\n", berisi[1]);
printf("Cetak kosong: [%s]\n", kosong);
printf("Cetak berisi: [%s]\n", berisi);
return 0;
}Output yang Dihasilkan:
sizeof kosong = 1
sizeof berisi = 2
kosong[0] = 0
berisi[0] = A (ASCII 65)
berisi[1] = 0 (null terminator)
Cetak kosong: []
Cetak berisi: [A]sizeof kosong = 1 karena satu-satunya byte adalah null terminator. sizeof berisi = 2 karena ada satu karakter A plus null terminator. Ketika printf("%s", kosong) dipanggil, printf langsung menemui byte 0 di posisi pertama sehingga tidak ada karakter yang dicetak—output hanyalah baris kosong. Inilah mengapa string kosong sering dipakai sebagai initial state buffer: satu byte '\0' sudah cukup membentuk string valid.
Ringkasan Konsep #
Null terminator adalah byte '\0' (nilai numerik 0) yang berfungsi sebagai sentinel penanda akhir string. Seluruh string library C bergantung pada konvensi ini—mereka mengiterasi byte sampai menemui null terminator untuk mengetahui kapan berhenti. Konsekuensi praktisnya ada tiga: (1) setiap buffer string wajib menyediakan satu byte ekstra untuk null terminator; (2) setiap penulisan karakter baru ke string harus diikuti penulisan '\0' di posisi akhir; dan (3) string kosong adalah array satu byte yang berisi '\0'. Pada chapter berikutnya kita akan melihat bagaimana null terminator otomatis ditambahkan oleh compiler pada string literal dan bagaimana cara mendeklarasikan string dengan berbagai pola.