Compiler peringatan mengawasi kode pada saat kompilasi, tetapi sebagian bug baru menyala saat program benar-benar berjalan — akses melampaui batas array, pemakaian pointer yang sudah dilepas, luapan aritmetika, atau dua thread yang saling menyerobot. Untuk kelas bug inilah sanitizer dibuat: modul pemeriksa runtime yang ditanam compiler ke dalam program Anda, siap membunyikan alarm pada detik pelanggaran pertama dengan laporan yang menunjuk baris pelakunya. g++ 14.2 membawa tiga sanitizer utama (ASan, UBSan, TSan) tanpa instalasi tambahan, sehingga kebiasaan ini bisa kita bangun mulai hari ini juga.

Prinsip Kerja: Instrumentasi Kode #

Sanitizer bukan program pemeriksa eksternal — compiler menggenapi kode kita dengan kode pemeriksa tambahan (instrumentasi). Setiap akses memori yang dikompilasi dengan -fsanitize=address, misalnya, disisipi panggilan yang memastikan alamat tersebut berada dalam wilayah yang sah; setiap operasi aritmetika yang dikompilasi dengan -fsanitize=undefined diperiksa terhadap syarat standar C++. Inilah sebabnya sanitizer menangkap bug dengan presisi baris kode, bukan sekadar gejala seperti “program tiba-tiba mati”: laporannya memuat jenis pelanggaran, alamat memori, isi stack pada saat itu, dan alokasi yang bersangkutan. Konsekuensinya juga jelas: program hasil instrumentasi lebih lambat dan lebih rakus memori daripada versi biasa, dan untuk itu kita memisahkan build sanitizer dari build produksi.

Empat Anggota Keluarga #

SanitizerFlagBug yang dideteksi
AddressSanitizer (ASan)-fsanitize=addressheap/stack/global-buffer-overflow, use-after-free, double-free
LeakSanitizer (LSan)bagian dari ASanmemory leak — alokasi yang tak pernah dilepas
UndefinedBehaviorSanitizer (UBSan)-fsanitize=undefinedsigned overflow, shift tak wajar, pointer tak sejajar (misaligned)
ThreadSanitizer (TSan)-fsanitize=threaddata race antar thread

Karena masing-masing menambah instrumentasi berbeda, sanitizer bisa digabung: -fsanitize=address,undefined adalah kombinasi dev yang populer untuk program non-multithread. Kombinasi address dan thread, sebaliknya, tidak didukung secara bersamaan — keduanya menggantikan alokator dan mekanisme thread dengan cara yang berbenturan, jadi jalankan keduanya pada build terpisah.

Menjalankan Program Sehat di Bawah Sanitizer #

Target kita program kecil yang bekerja di heap — dialokasikan, diisi, dibaca, lalu dilepas dengan benar:

cpp
// proses-data.cpp — target kecil untuk dicoba dengan berbagai sanitizer.

#include <iostream>
#include <string>
#include <vector>

int jumlahKarakter(const std::vector<std::string>& kata) {
    int total = 0;
    for (const std::string& s : kata) {
        total += static_cast<int>(s.size());
    }
    return total;
}

int main() {
    std::vector<std::string> kata = {"debugging", "sanitizer", "runtime"};

    int* panjang = new int[kata.size()];
    for (std::size_t i = 0; i < kata.size(); ++i) {
        panjang[i] = static_cast<int>(kata[i].size());
    }

    std::cout << "jumlah karakter: " << jumlahKarakter(kata) << "\n";
    std::cout << "panjang pertama: " << panjang[0] << "\n";

    delete[] panjang;
    return 0;
}

Output yang Dihasilkan:

text
jumlah karakter: 25
panjang pertama: 9

Sekarang bandingkan dua cara build dan jalankan. Versi biasa sudah Anda kenal; versi sanitizer menyisipkan pemeriksa ke setiap akses memori dan operasi aritmetika:

bash
g++ -std=c++23 -Wall -Wextra proses-data.cpp -o proses-data
g++ -std=c++23 -Wall -Wextra -g -fsanitize=address,undefined proses-data.cpp -o proses-data-asan
./proses-data-asan

Program yang sehat menghasilkan output yang persis sama seperti di atas, dengan kode akhir 0 — dan tidak ada laporan apa pun. Inilah kontrak sanitizer: diam berarti bersih. Bila salah satu akses melanggar aturan, satu laporan detail tercetak ke stderr dan program dihentikan dengan kode akhir tidak nol — perilaku yang mudah ditangkap oleh skrip otomatis. Flag -g di perintah kedua bukan bagian sanitizer, tetapi pasangan wajibnya: tanpa informasi debug, laporan hanya memuat alamat mesin tanpa nama berkas dan nomor baris.

Overhead dan Kapan Sanitizer Dipakai #

Instrumentasi menuntut bayar, dan besarnya berbeda per sanitizer:

  • ASan: umumnya program berjalan kira-kira dua kali lebih lambat dengan pemakaian memori yang juga naik; masih sangat nyaman untuk kerja harian.
  • UBSan: overhead kecil — sering nyaris tak terasa — sehingga boleh menyala terus selama pengembangan.
  • TSan: yang paling berat, bisa berkali-kali lipat lebih lambat, karena setiap akses memori dicatat untuk mendeteksi pola antar thread.

Aturan penempatannya:

  1. Lokal (dev): build harian dengan -fsanitize=address,undefined saat menjalankan test suite — bug memori tertangkap menit-menit setelah lahir, bukan berminggu-minggu kemudian.
  2. CI: satu job khusus yang mengompilasi seluruh test suite dengan ASan (dan TSan bila proyek multithread); kegagalan sanitizer menggagalkan merge.
  3. Produksi: build release tanpa sanitizer, karena overhead tidak layak untuk pengguna akhir. Kecuali — pola yang umum di industri — build khusus dengan LSan/ASan yang dijalankan berkala pada server internal untuk menangkap leak yang hanya muncul setelah berjam-jam.

Dengan peta keluarga sanitizer ini, tiga chapter berikutnya membedah satu per satu anggotanya yang paling sering menyelamatkan proyek: AddressSanitizer untuk error memori, UndefinedBehaviorSanitizer untuk aritmetika dan pointer, serta ThreadSanitizer untuk data race.

Referensi: GCC — Instrumentation Options (-fsanitize) , Google sanitizers .