Proyek C nyata biasanya membutuhkan beberapa konfigurasi build yang berbeda untuk keperluan berbeda: build Debug untuk pengembangan sehari-hari (optimasi rendah, informasi debug lengkap), build Release untuk produksi (optimasi tinggi, tanpa assertion), dan RelWithDebInfo untuk profil produksi yang tetap bisa didebug. CMake mengelola konfigurasi-konfigurasi ini melalui variabel CMAKE_BUILD_TYPE. Pada chapter ini kita akan membandingkan flag yang dihasilkan tiap konfigurasi, mengamati efek pada ukuran binary, dan melihat bagaimana macro NDEBUG mengubah perilaku runtime program.
Empat Build Type Bawaan CMake #
CMake mendefinisikan empat build type standar yang masing-masing menghasilkan set flag compiler berbeda:
| Build Type | Flag Compiler | Tujuan |
|---|---|---|
Debug | -g | Pengembangan, optimasi rendah, debug info |
Release | -O3 -DNDEBUG | Produksi, optimasi agresif, assertion off |
RelWithDebInfo | -O2 -g -DNDEBUG | Produksi yang masih bisa didebug |
MinSizeRel | -Os -DNDEBUG | Produksi, ukuran binary minimum |
Variabel CMAKE_BUILD_TYPE diatur saat konfigurasi lewat -D. Setiap direktori build hanya boleh punya satu build type — bila kita ingin membangun dua konfigurasi, buat dua direktori terpisah (build_debug/ dan build_release/).
Kode Demo #
Kode berikut memeriksa apakah macro NDEBUG didefinisikan. CMake secara otomatis mendefinisikan NDEBUG untuk build type Release, RelWithDebInfo, dan MinSizeRel:
#include <stdio.h>
#ifdef NDEBUG
#define DEBUG_STATUS "NDEBUG defined (assertions disabled)"
#else
#define DEBUG_STATUS "NDEBUG NOT defined (assertions enabled)"
#endif
int sum_array(const int *a, int n) {
int total = 0;
for (int i = 0; i < n; i++) {
total += a[i];
}
return total;
}
int main(void) {
int a[1000];
for (int i = 0; i < 1000; i++) {
a[i] = i + 1;
}
int hasil = sum_array(a, 1000);
printf("sum 1..1000 = %d\n", hasil);
printf("build: %s\n", DEBUG_STATUS);
return 0;
}CMakeLists.txt minimal untuk proyek ini:
cmake_minimum_required(VERSION 3.10)
project(build-config-demo C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
add_executable(app main.c)
target_compile_options(app PRIVATE -Wall -Wextra)Build Debug #
cmake -B build_debug -DCMAKE_BUILD_TYPE=Debug
cmake --build build_debug
./build_debug/appOutput yang Dihasilkan:
-- Configuring done (0.1s)
-- Generating done (0.0s)
-- Build files have been written to: /tmp/test_build_config/build_debug
[ 50%] Building C object CMakeFiles/app.dir/main.c.o
[100%] Linking C executable app
[100%] Built target app
sum 1..1000 = 500500
build: NDEBUG NOT defined (assertions enabled)Baris terakhir menunjukkan NDEBUG tidak didefinisikan — assertion (assert() dari <assert.h>) akan aktif saat runtime. Mari kita lihat command line compiler yang sebenarnya dipakai dengan memeriksa compile_commands.json:
cmake -B build_debug -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
cat build_debug/compile_commands.json | python3 -c "import json,sys; print(json.load(sys.stdin)[0]['command'])"Output yang Dihasilkan:
/usr/bin/cc -g -std=gnu11 -Wall -Wextra -o CMakeFiles/app.dir/main.c.o -c /tmp/test_build_config/main.cFlag yang aktif di build Debug:
-g: informasi debug DWARF — diperlukan olehgdbuntuk menampilkan nama variabel dan nomor baris.-std=gnu11: CMake menambahkan flag standar C (dialek gnu11 karenaCMAKE_C_EXTENSIONSdefault ON).-Wall -Wextra: flag daritarget_compile_optionsdiCMakeLists.txt.- Tidak ada
-O: tidak ada optimasi. Variabel lokal disimpan di stack persis seperti tertulis di kode — mudah diperiksa di debugger.
Build Release #
cmake -B build_release -DCMAKE_BUILD_TYPE=Release
cmake --build build_release
./build_release/appOutput yang Dihasilkan:
sum 1..1000 = 500500
build: NDEBUG defined (assertions disabled)Periksa command line compiler:
cat build_release/compile_commands.json | python3 -c "import json,sys; print(json.load(sys.stdin)[0]['command'])"Output yang Dihasilkan:
/usr/bin/cc -O3 -DNDEBUG -std=gnu11 -Wall -Wextra -o CMakeFiles/app.dir/main.c.o -c /tmp/test_build_config/main.cPerbedaan utama dengan Debug:
-O3: optimasi agresif — loop unrolling, function inlining, vectorization. Kode berjalan lebih cepat tetapi susah didebug karena variabel dioptimasi keluar dari stack.-DNDEBUG: mendefinisikan macroNDEBUG. Header<assert.h>memeriksa macro ini — bilaNDEBUGterdefinisi,assert(expr)dievaluasi menjadi kosong (tidak ada kode runtime). Ini menghilangkan overhead assertion di produksi.- Tidak ada
-g: tidak ada informasi debug. Ukuran binary lebih kecil.
Build RelWithDebInfo #
cmake -B build_relwithdebinfo -DCMAKE_BUILD_TYPE=RelWithDebInfo
cmake --build build_relwithdebinfo
./build_relwithdebinfo/appOutput yang Dihasilkan:
sum 1..1000 = 500500
build: NDEBUG defined (assertions disabled)Periksa command line:
cat build_relwithdebinfo/compile_commands.json | python3 -c "import json,sys; print(json.load(sys.stdin)[0]['command'])"Output yang Dihasilkan:
/usr/bin/cc -O2 -g -DNDEBUG -std=gnu11 -Wall -Wextra -o CMakeFiles/app.dir/main.c.o -c /tmp/test_build_config/main.cRelWithDebInfo adalah kompromi antara Debug dan Release:
-O2: optimasi standar rilis — cepat tetapi tidak se-agresif-O3.-g: informasi debug disertakan — kita bisa memakaigdbuntuk memeriksa crash di produksi (meskipun beberapa variabel mungkin sudah dioptimasi keluar).-DNDEBUG: assertion dimatikan sepertiRelease.
Build type ini cocok untuk profil production crash investigation — kita mengirim binary dengan info debug ke lapangan, lalu saat crash terjadi, gdb bisa menampilkan stack trace dengan nomor baris.
Build MinSizeRel #
cmake -B build_minsizerel -DCMAKE_BUILD_TYPE=MinSizeRel
cmake --build build_minsizerel
./build_minsizerel/appOutput yang Dihasilkan:
sum 1..1000 = 500500
build: NDEBUG defined (assertions disabled)Periksa command line:
cat build_minsizerel/compile_commands.json | python3 -c "import json,sys; print(json.load(sys.stdin)[0]['command'])"Output yang Dihasilkan:
/usr/bin/cc -Os -DNDEBUG -std=gnu11 -Wall -Wextra -o CMakeFiles/app.dir/main.c.o -c /tmp/test_build_config/main.cMinSizeRel memakai -Os — optimasi untuk ukuran kode terkecil. Compiler menghindari optimasi yang memperbesar binary (loop unrolling, function inlining agresif). Cocok untuk embedded device dengan flash storage terbatas atau distribusi binary ke jaringan lambat.
Perbandingan Ukuran Binary #
Bandingkan ukuran executable yang dihasilkan tiap build type:
ls -l build_debug/app build_release/app build_relwithdebinfo/app build_minsizerel/app | awk '{print $5, $9}'Output yang Dihasilkan:
17560 build_debug/app
18552 build_relwithdebinfo/app
15984 build_minsizerel/app
15984 build_release/appMari kita analisis angka-angka tersebut:
Debug(17560 byte): paling besar di antara yang tanpa optimasi. Incl. info debug DWARF yang menyimpan mapping baris, tabel simbol, dan tipe data. Instruksi mesin tidak dioptimasi sehingga sedikit lebih panjang dari-O2.Release(15984 byte): ukuran terkecil karena-O3mengoptimalkan kode.RelWithDebInfo2,5 KB lebih besar dariReleasemurni karena info debug.MinSizeRel(15984 byte): ukuran sama denganReleasedi proyek kecil ini. Pada proyek besar,MinSizeRelbiasanya menghasilkan binary 5-15% lebih kecil dariReleasekarena tidak melakukan loop unrolling.RelWithDebInfo(18552 byte): paling besar karena gabungan optimasi dan info debug. Tambahan 2,5 KB dibandingReleaseadalah tabel DWARF untuk memetakan instruksi ke baris kode sumber.