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 #
| Sanitizer | Flag | Bug yang dideteksi |
|---|---|---|
| AddressSanitizer (ASan) | -fsanitize=address | heap/stack/global-buffer-overflow, use-after-free, double-free |
| LeakSanitizer (LSan) | bagian dari ASan | memory leak — alokasi yang tak pernah dilepas |
| UndefinedBehaviorSanitizer (UBSan) | -fsanitize=undefined | signed overflow, shift tak wajar, pointer tak sejajar (misaligned) |
| ThreadSanitizer (TSan) | -fsanitize=thread | data 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:
// 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:
jumlah karakter: 25
panjang pertama: 9Sekarang bandingkan dua cara build dan jalankan. Versi biasa sudah Anda kenal; versi sanitizer menyisipkan pemeriksa ke setiap akses memori dan operasi aritmetika:
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-asanProgram 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:
- Lokal (dev): build harian dengan
-fsanitize=address,undefinedsaat menjalankan test suite — bug memori tertangkap menit-menit setelah lahir, bukan berminggu-minggu kemudian. - CI: satu job khusus yang mengompilasi seluruh test suite dengan ASan (dan TSan bila proyek multithread); kegagalan sanitizer menggagalkan merge.
- 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 .