GCC dan Clang menerima ratusan flag command-line. Mayoritas flag tersebut jarang dipakai, tetapi segelintir di antaranya wajib kita kuasai karena hampir setiap proyek C menggunakannya. Pada chapter ini kita akan membahas flag yang paling sering muncul di Makefile dan CMakeLists.txt: -std untuk memilih versi bahasa C, -Wall dan -Wextra untuk mengaktifkan peringatan, -O untuk tingkat optimasi, -g untuk informasi debug, serta -I, -L, dan -l untuk mencari header dan library. Memahami flag-flag ini membuat kita bisa membaca konfigurasi build proyek orang lain tanpa kebingungan.
Memilih Standar Bahasa: -std=c11 #
GCC secara default menggunakan standar gnu17 (dialek GCC dari C17) — bukan C murni. Untuk memilih standar tertentu, kita gunakan flag -std=. Nilai yang umum: c99, c11, c17, dan c23.
#include <stdio.h>
int sum_to(int n) {
int total = 0;
for (int i = 1; i <= n; i++) {
total += i;
}
return total;
}
int main(void) {
int hasil = sum_to(100);
printf("sum 1..100 = %d\n", hasil);
return 0;
}Kompilasi dengan standar C11 dan jalankan:
gcc -std=c11 demo.c -o demo && ./demoOutput yang Dihasilkan:
sum 1..100 = 5050Peringatan: -Wall dan -Wextra #
Flag -Wall mengaktifkan banyak peringatan yang seharusnya selalu diaktifkan (meski bukan semua peringatan — namanya menyesatkan). Flag -Wextra menambahkan peringatan ekstra yang tidak masuk -Wall, misalnya parameter fungsi yang tidak dipakai atau perbandingan signed/unsigned. Praktik baik industri: selalu pakai keduanya, dan perlakukan warning sebagai error dengan -Werror bila perlu.
gcc -std=c11 -Wall -Wextra demo.c -o demoOutput yang Dihasilkan:
(sum 1..100 = 5050 saat dijalankan; kompilasi bersih tanpa peringatan)Bila kita sengaja membuat bug kecil seperti variabel tidak dipakai, -Wextra akan menunjukkannya:
int main(void) {
int x; /* tidak dipakai */
printf("halo\n");
return 0;
}gcc -std=c11 -Wall -Wextra unused.c -o unusedOutput yang Dihasilkan:
unused.c: In function 'main':
unused.c:2:9: warning: unused variable 'x' [-Wunused-variable]
2 | int x; /* tidak dipakai */
| ^Mari kita telaah baris-baris output di atas:
- Baris pertama (
unused.c: In function 'main':): GCC menunjukkan file dan fungsi tempat warning ditemukan, sehingga kita langsung tahu konteksnya. - Baris kedua (
unused.c:2:9: warning: unused variable 'x' [-Wunused-variable]): formatfile:baris:kolom: warning: pesan [nama-warning]. Kolom9menunjuk tepat ke posisi variabelx. Nama-Wunused-variablebisa dipakai untuk menonaktifkan warning tertentu secara selektif dengan-Wno-unused-variable. - Baris ketiga (
2 | int x;): GCC menampilkan ulang baris kode sumber dengan nomor baris. - Baris keempat (
| ^): tanda^menunjuk ke kolom ke-9, di bawah variabelx.
Tingkat Optimasi: -O0, -O1, -O2, -O3 #
Flag -O (huruf O besar) mengatur seberapa agresif compiler mengoptimasi kode. Tingkat yang umum:
-O0(default): tidak ada optimasi. Cocok untuk debugging karena variabel tetap di stack dan urutan eksekusi persis seperti tertulis di kode.-O1: optimasi ringan, mengurangi ukuran kode dan waktu eksekusi tanpa mengorbankan waktu kompilasi.-O2: optimasi standar rilis. Mayoritas proyek memakai tingkat ini.-O3: optimasi agresif (loop unrolling, vectorization). Kode menjadi lebih cepat tetapi ukuran binary membengkak.-Os: optimasi untuk ukuran kode terkecil.-Og: optimasi ringan yang ramah debugging (sejak GCC 4.8).
Mari kita lihat bagaimana tingkat optimasi mempengaruhi kode yang dihasilkan:
#include <stdio.h>
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;
}
printf("sum 1..1000 = %d\n", sum_array(a, 1000));
return 0;
}for opt in O0 O1 O2 O3; do
gcc -$opt -S opt_demo.c -o opt_demo_$opt.s
lines=$(wc -l < opt_demo_$opt.s)
echo "-$opt: $lines baris assembly"
doneOutput yang Dihasilkan:
-O0: 84 baris assembly
-O1: 62 baris assembly
-O2: 108 baris assembly
-O3: 148 baris assemblyTampak bahwa -O1 menurunkan jumlah baris (compiler menghapus operasi stack yang tidak perlu), sementara -O2 dan -O3 justru menaikkan jumlah baris karena loop unrolling, alignment directive, dan vectorization memperbesar kode meski kode tersebut biasanya berjalan lebih cepat.
Mari kita bandingkan loop inti fungsi sum_array pada -O0 dan -O2:
# -O0
.L3:
movl -8(%rbp), %eax ; load i dari stack
cltq
leaq 0(,%rax,4), %rdx ; alamat a[i] dihitung dari awal setiap iterasi
movq -24(%rbp), %rax ; load pointer a dari stack
addq %rdx, %rax
movl (%rax), %eax ; load a[i]
addl %eax, -4(%rbp) ; tambahkan ke total di stack
addl $1, -8(%rbp) ; i++
.L2:
movl -8(%rbp), %eax
cmpl -28(%rbp), %eax ; bandingkan i dengan n (load dari stack)
jl .L3# -O2
.L3:
addl (%rdi), %eax ; total += *p
addq $4, %rdi ; p++
cmpq %rdx, %rdi ; bandingkan p dengan end (register)
jne .L3
retVersi -O0 membaca/menulis i, total, dan pointer a dari stack di setiap iterasi — empat kali akses memori per loop. Versi -O2 menggunakan register eax untuk akumulator, rdi untuk pointer berjalan, dan rdx untuk end pointer; tidak ada akses stack di dalam loop. Inilah inti perbedaan optimasi: compiler menyusun ulang akses memori agar sesedikit mungkin menyentuh RAM.
Informasi Debug: -g #
Flag -g menyisipkan informasi debug format DWARF ke dalam binary. Informasi ini dipakai oleh gdb, valgrind, dan profiler untuk memetakan balik instruksi mesin ke baris kode C sumber. Tanpa -g, debugger hanya bisa menampilkan alamat memori, bukan nama variabel atau nomor baris.
gcc -g -std=c11 demo.c -o demo_debug
ls -l demo_debug | awk '{print $5, $9}'Output yang Dihasilkan:
17360 demo_debugBandingkan dengan binary tanpa -g:
gcc -std=c11 demo.c -o demo_nodebug
ls -l demo_nodebug | awk '{print $5, $9}'Output yang Dihasilkan:
15984 demo_nodebugSelisih 1376 byte adalah tabel DWARF yang berisi mapping baris, tabel simbol lokal, dan tipe data. Untuk build rilis, kita biasanya memakai -O2 -g sekaligus lalu memangkas informasi debug dengan strip bila ingin binary yang lebih ramping.
Mencari Header dan Library: -I, -L, -l #
Ketiga flag ini mengontrol pencarian header dan library:
-I<path>: tambahkanpathke daftar direktori pencarian header untuk#include <...>.-L<path>: tambahkanpathke daftar direktori pencarian library.-l<name>: link dengan library bernamalib<name>.soataulib<name>.a.
Mari kita demonstrasikan -I dengan header kustom:
mkdir -p include
echo '#define VERSION "1.0.0-demo"' > include/myheader.h
cat > use_header.c <<'EOF'
#include <stdio.h>
#include <myheader.h>
int main(void) {
printf("Versi: %s\n", VERSION);
return 0;
}
EOF
# Tanpa -Iinclude (harus gagal):
gcc -std=c11 use_header.c -o use_header_noI
# Dengan -Iinclude (harus sukses):
gcc -std=c11 -Wall -Iinclude use_header.c -o use_header && ./use_headerOutput yang Dihasilkan:
# Tanpa -Iinclude:
use_header.c:2:10: fatal error: myheader.h: No such file or directory
2 | #include <myheader.h>
| ^~~~~~~~~~~~
compilation terminated.
# Dengan -Iinclude:
Versi: 1.0.0-demoUntuk demonstrasi -L dan -l, kita akan melihatnya secara konkret pada chapter Static Library dan Shared Library.