Program yang sama, dikompilasi dengan flag berbeda, bisa menjadi dua biner yang bertingkah laku berbeda — dan inilah salah satu sumber bug paling licik di C++. Saat mengembangkan kita menginginkan build yang cepat dikompilasi dan ramah debugger; saat mengirim ke pengguna kita menginginkan build yang cepat berjalan dan kecil. Dunia build system menyebut keduanya konfigurasi Debug dan Release, dan keduanya dibedakan oleh sekumpulan flag compiler yang setiap anggotanya layak Anda kenali satu per satu: -O untuk tingkat optimasi, -g untuk informasi debug, dan -DNDEBUG yang diam-diam menghapus pemeriksaan assert. Semua flag di chapter ini diuji langsung dengan g++ di lingkungan praktikum.
Debug dan Release di CMake #
CMake membungkus konsep ini dalam variabel CMAKE_BUILD_TYPE yang dipilih saat configure:
cmake -B build -DCMAKE_BUILD_TYPE=Debug
cmake -B build -DCMAKE_BUILD_TYPE=ReleaseTiap nama memetakan ke set flag compiler bawaan — untuk GCC di sistem mirip Unix:
CMAKE_BUILD_TYPE | Flag GCC | Tujuan |
|---|---|---|
Debug | -g | Pengembangan: berisi informasi debug, tanpa optimasi |
Release | -O3 -DNDEBUG | Distribusi: optimasi penuh, pemeriksaan dihapus |
RelWithDebInfo | -O2 -g -DNDEBUG | Release yang masih bisa di-debug |
MinSizeRel | -Os -DNDEBUG | Ukuran file terkecil |
Perhatikan bahwa -DNDEBUG hadir di ketiga konfigurasi non-Debug — bukan kebetulan, dan nanti kita buktikan akibatnya. Tanpa pemilihan eksplisit, CMake tidak menetapkan build type apa pun (flag kosong), kebiasaan yang mengejutkan banyak pemula: proyek yang “berjalan lambat sekali” sering kali hanya belum diberi CMAKE_BUILD_TYPE=Release.
Tingkat Optimasi: -O0 sampai -Os #
Flag -O mengatur seberapa jauh compiler boleh menyusun ulang kode kita demi kecepatan atau ukuran:
| Flag | Arti | Khas dipakai untuk |
|---|---|---|
-O0 | Tanpa optimasi — kode dieksekusi apa adanya | Debug; kompilasi tercepat |
-O1 | Optimasi ringan | Kompromi kecepatan kompilasi/hasil |
-O2 | Optimasi standar — inlining, unrolling, vektorisasi dasar | Kebanyakan software rilis |
-O3 | Optimasi agresif — vektorisasi lebih jauh | Program yang dibatasi kecepatan hitung |
-Os | Optimasi untuk ukuran, bukan kecepatan | Firmware, alat dengan disk/memori kecil |
Untuk melihat efeknya, kita pakai program kecil yang menghitung jumlah bilangan prima di bawah 50.000 — murni aritmetika integer, sehingga hasilnya wajib identik di semua tingkat optimasi:
#include <iostream>
// Menghitung berapa banyak bilangan prima di bawah batas.
// Murni aritmetika integer: hasilnya identik pada semua tingkat -O.
bool apakahPrima(int n) {
if (n < 2) return false;
for (int p = 2; p * p <= n; ++p) {
if (n % p == 0) return false;
}
return true;
}
int main() {
const int batas = 50000;
int jumlah = 0;
for (int n = 2; n < batas; ++n) {
if (apakahPrima(n)) ++jumlah;
}
std::cout << "Bilangan prima di bawah " << batas << ": " << jumlah << "\n";
return 0;
}Output yang Dihasilkan:
Bilangan prima di bawah 50000: 5133Kini kompilasi empat varian dan jalankan semuanya — log nyata dari lingkungan praktikum:
g++ -std=c++23 -Wall -Wextra -O0 -g demo-optimasi.cpp -o demo-debug
g++ -std=c++23 -Wall -Wextra -O2 demo-optimasi.cpp -o demo-O2
g++ -std=c++23 -Wall -Wextra -O3 demo-optimasi.cpp -o demo-O3
g++ -std=c++23 -Wall -Wextra -Os demo-optimasi.cpp -o demo-Os
./demo-debug
./demo-O2
./demo-O3
./demo-OsOutput yang Dihasilkan:
Bilangan prima di bawah 50000: 5133
Bilangan prima di bawah 50000: 5133
Bilangan prima di bawah 50000: 5133
Bilangan prima di bawah 50000: 5133Keempat biner menghitung sama persis — optimasi tidak boleh mengubah hasil. Lalu bagaimana wujud fisiknya? Kolom ukuran dari ls -l (beserta namanya, dipilih lewat awk) pada build yang sama:
ls -l demo-debug demo-O2 demo-O3 demo-Os | awk '{print $5, $9}'Output yang Dihasilkan:
16224 demo-O2
16248 demo-O3
16224 demo-Os
104896 demo-debugBiner debug membengkak hingga enam kali lipat dibanding varian optimasi — sebagian besar karena -g. Untuk program sekecil ini selisih -O2, -O3, dan -Os tipis; pola perbedaannya yang penting diingat, bukan angka byte-nya (yang di mesin Anda bisa sedikit berbeda karena versi compiler). Pengukuran kecepatan eksekusi antar varian kita serahkan ke Section Performa dan Optimasi yang punya alat ukurnya sendiri.
Informasi Debug dengan -g #
Flag -g tidak mengubah cara program berjalan sama sekali — ia menambah informasi debug: tabel yang memetakan kode mesin kembali ke baris source, nama variabel, dan tipe. Database inilah yang dibaca debugger untuk menampilkan “Anda sedang di baris 17, variabel jumlah bernilai 5133”. Bayarnya ukuran file:
g++ -std=c++23 -Wall -Wextra -O0 demo-optimasi.cpp -o demo-O0
ls -l demo-O0 demo-debug | awk '{print $5, $9}'Output yang Dihasilkan:
22096 demo-O0
104896 demo-debugTanpa -g, biner -O0 hanya 22.096 byte; dengan -g, 104.896 byte. Karena informasi debug tidak berpengaruh ke perilaku, kebiasaan yang sehat adalah: -g selalu menyertai build pengembangan, dan build distribusi yang butuh penelusuran masalah di lapangan memilih RelWithDebInfo — optimasi penuh dengan info debug tetap dibawa.
NDEBUG: Ketika assert Ikut Hilang #
Makro assert — pemeriksaan kondisi yang menghentikan program bila kondisinya salah — hanya aktif ketika makro NDEBUG tidak didefinisikan. Kompilasi Release mendefinisikannya, sehingga seluruh ekspresi assert(...) diganti baris kosong oleh preprocessor. Program berikut memakai assert untuk menagih validitas input:
#include <cassert>
#include <iostream>
#include <string>
int main(int argc, char* argv[]) {
int skor = (argc > 1) ? std::stoi(argv[1]) : 85;
// Pemeriksaan input yang wajib benar: skor berada pada rentang 0..100.
assert(skor >= 0 && skor <= 100);
std::cout << "Skor tercatat: " << skor << "\n";
return 0;
}Output yang Dihasilkan:
Skor tercatat: 85Kini kompilasi dua varian dan uji keduanya dengan input yang melanggar kontrak — skor 150:
g++ -std=c++23 -Wall -Wextra -O0 -g cek-batas.cpp -o cek-debug
g++ -std=c++23 -Wall -Wextra -O2 -DNDEBUG cek-batas.cpp -o cek-release
./cek-debug 150
echo $?Output yang Dihasilkan:
cek-debug: cek-batas.cpp:9: int main(int, char**): Assertion `skor >= 0 && skor <= 100' failed.
134Versi debug berhenti paksa di baris 9 — nama file dan nomor barisnya menunjuk sumber pada saat kompilasi — dengan exit code 134 (128 ditambah kode sinyal SIGABRT; pesan Aborted yang menyertainya tampil beda antar shell). Sekarang biner release dengan input yang sama:
./cek-release 150
echo $?Output yang Dihasilkan:
Skor tercatat: 150
0Pemeriksaannya hilang begitu saja: skor 150 lolos dan program kembali dengan sukses. Inilah wajah dua sisi NDEBUG — build debug berperan sebagai jaring pengaman di tangan developer, build release tidak lagi membayar biaya pemeriksanya di produksi.
Memilih Konfigurasi #
- Sehari-hari menulis kode:
Debug— jaring pengaman assert penuh, debugger siap pakai, kompilasi cepat. - Continuous integration: build
Debuguntuk menjalankan tes, plusReleaseuntuk memastikan versi optimasi juga sehat. - Mengirim ke pengguna:
ReleaseatauMinSizeRel;RelWithDebInfobila laporan dari lapangan perlu ditelusuri.
Konfigurasi, dependensi, dan tes kini sudah lengkap sebagai bagian-bagian terpisah. Yang belum tergambar adalah wujud keseluruhan proyek: file mana taruhnya di folder mana, supaya semua bagian itu menemukan satu sama lain tanpa kekacauan. Chapter berikutnya, yang sekaligus menutup section ini, menyusun layout-nya.
Referensi: Optimize Options , Debugging Options , assert — cppreference .