Program C++ bisa gagal di tiga waktu yang berbeda: saat compiler menerjemahkan source code, saat linker menyatukan potongan-potongan objek, dan saat program yang sudah jadi berjalan di hadapan pengguna. Ketiganya sama-sama “error”, tetapi biaya dan cara menanganinya sangat berbeda. Kesalahan yang tertangkap compiler diperbaiki dalam hitungan detik; kesalahan yang baru muncul di komputer pengguna bisa menyebar jauh sebelum ada yang menyadarinya. Chapter pembuka Section Error Handling ini memetakan ketiga kategori tersebut dengan bukti nyata dari g++ 14.2, lalu menunjukkan satu alat yang membuat kita bisa ikut campur di waktu kompilasi: static_assert. Setelah peta ini jelas, kita akan punya dasar untuk memilih alat pelaporan kegagalan yang tepat pada chapter-chapter berikutnya.
Tiga Waktu Kesalahan Menampakkan Diri #
Sebelum masuk ke contoh, mari kita samakan dulu pemahamannya. Setiap kategori punya “wasit” yang berbeda dan moment penemuan yang berbeda:
| Kategori | Kapan terdeteksi | Siapa yang melaporkan | Contoh klasik |
|---|---|---|---|
| Compile error | Saat kompilasi | Compiler (g++) | Salah ketik nama, lupa titik koma, tipe tidak cocok |
| Link error | Saat linker menggabungkan objek | Linker (ld) | Function dideklarasikan tapi tidak pernah didefinisikan |
| Runtime error | Saat program berjalan | Program itu sendiri (atau sistem operasi) | Pembagian nol, elemen di luar jangkauan, berkas hilang |
Urutan pada tabel di atas juga urutan kebijakan yang akan kita ikuti sepanjang section ini: jika sebuah kesalahan bisa dideteksi compiler, jadikan ia compile error; jika tidak, sediakan mekanisme pelaporan yang jelas saat runtime.
Error Kompilasi: Laporan dari Compiler #
Compile error muncul ketika source code melanggar aturan bahasa, sehingga compiler menolak menghasilkan file objek. Program kecil berikut (kita sebut tanpa-titik-koma.cpp) sengaja kehilangan satu titik koma:
int jumlah(int a, int b) {
return a + b // lupa titik koma
}
int main() {
return jumlah(2, 3);
}Kompilasi file itu dengan g++ -std=c++23 -Wall -Wextra tanpa-titik-koma.cpp -o tanpa-titik-koma menghasilkan pesan berikut (hasil eksekusi nyata):
tanpa-titik-koma.cpp: In function ‘int jumlah(int, int)’:
tanpa-titik-koma.cpp:2:17: error: expected ‘;’ before ‘}’ token
2 | return a + b // lupa titik koma
| ^
| ;
3 | }
| ~Pesan ini bisa dibaca seperti alamat: tanpa-titik-koma.cpp:2:17 berarti baris 2, kolom 17; error: expected ';' menyatakan jenis masalahnya; dan compiler bahkan menunjukkan perbaikannya — karakter ; yang digambar di bawah tanda ^. Selama compiler menolak kode, tidak ada program yang lahir, dan tidak ada pengguna yang terdampak. Inilah alasan compile error adalah jenis kegagalan yang paling murah untuk diperbaiki.
Error Link: Symbol yang Tidak Pernah Didefinisikan #
Link error lebih licik karena kodenya sah di mata compiler. Aturan C++ memisahkan declaration (janji bahwa sebuah function ada) dari definition (isi function itu sendiri). Compiler percaya pada janji dan meluluskan kode berikut, tetapi linker kemudian mencari isi hitungDiskon dan tidak menemukannya:
double hitungDiskon(double harga); // dideklarasikan, tidak pernah didefinisikan
int main() {
double hargaBayar = hitungDiskon(100.0);
return hargaBayar > 0.0 ? 0 : 1;
}Kompilasi dan link satu langkah dengan g++ -std=c++23 -Wall -Wextra tanpa-definisi.cpp -o tanpa-definisi berakhir dengan pesan linker (hasil eksekusi nyata):
/usr/bin/ld: /tmp/cckoVYm1.o: in function `main':
tanpa-definisi.cpp:(.text+0x15): undefined reference to `hitungDiskon(double)'
collect2: error: ld returned 1 exit statusPesan kuncinya adalah undefined reference to 'hitungDiskon(double)': janji ada, isinya tidak. Pola ini sering terjadi nyata saat prototipe function diubah di satu header tetapi definisi di file lain lupa ikut disesuaikan, atau saat kita lupa menambahkan file .cpp baru ke daftar build. Detail tentang pembagian tugas compiler dan linker dibedah pada Section Persiapan Lingkungan; di sini cukup dicatat bahwa kesalahan ini masih tertangkap sebelum program pernah dijalankan.
Error Runtime: Gagal yang Baru Terlihat Saat Program Berjalan #
Runtime error adalah kegagalan yang compiler dan linker sama-sama luluskan, karena aturan bahasanya tidak dilanggar — yang dilanggar adalah kontrak program itu sendiri, misalnya “jangan membagi dengan nol” atau “indeks harus di dalam jangkauan”. Program berikut menghitung rata-rata; kontraknya adalah pemanggil harus memberi vector yang tidak kosong, dan kontrak itu baru dapat diperiksa saat program berjalan:
#include <iostream>
#include <vector>
// Menghitung rata-rata nilai. Kontraknya: pemanggil wajib memberi vector
// yang tidak kosong. Kontrak ini TIDAK bisa diperiksa compiler.
int rataRata(const std::vector<int>& nilai) {
int total = 0;
for (int n : nilai) {
total += n;
}
return total / static_cast<int>(nilai.size());
}
int main() {
std::vector<int> angka{80, 90, 100};
std::cout << "rata-rata: " << rataRata(angka) << "\n";
std::vector<int> kosong;
if (kosong.empty()) {
// Kesalahan ini baru terdeteksi saat program BERJALAN.
std::cout << "GALAT RUNTIME: pembagian dengan jumlah elemen 0\n";
return 1; // exit code bukan nol = sinyal kegagalan
}
std::cout << "rata-rata kosong: " << rataRata(kosong) << "\n";
return 0;
}Output yang Dihasilkan:
rata-rata: 90
GALAT RUNTIME: pembagian dengan jumlah elemen 0Bagian penting dari program ini:
- Baris 9 (
return total / ...): pembagian dengannilai.size()sah menurut bahasa; compiler tidak punya cara mengetahui apakahsize()bernilai nol saat program berjalan. - Baris 17-20: jalur pemanggilan pertama normal — rata-rata
90tercetak dan eksekusi berlanjut. - Baris 22-26 (pemeriksaan
kosong.empty()): deteksi kegagalan dilakukan secara eksplisit di runtime, lalu dilaporkan dengan pesan dan exit code1(atributexit="1"pada blok kode meminta alat verifikasi mengharapkan exit code itu). - Baris 25 (
return 1;): exit code bukan nol adalah konvensi umum untuk melaporkan kegagalan kepada sistem operasi dan skrip pemanggil.
Perhatikan bahwa program ini masih “jujur”: ia memeriksa kontraknya sendiri dan berhenti dengan pesan yang jelas. Versi tanpa pemeriksaan akan melakukan pembagian bilangan bulat dengan nol — undefined behavior — dan hasilnya tidak terdefinisi. Detail kategori berbahaya itu kita bahas khusus pada Chapter Undefined Behavior.
Menggeser Kesalahan ke Compile Time dengan static_assert #
Jika runtime error lebih mahal daripada link error, dan link error lebih mahal daripada compile error, strategi yang sehat jelas: geser sebanyak mungkin pemeriksaan ke waktu paling awal yang memungkinkan. Untuk asumsi tentang tipe dan platform, C++ menyediakan static_assert — pernyataan yang dievaluasi compiler saat kompilasi, bukan saat program berjalan. Bila kondisinya salah, kompilasi langsung ditolak:
#include <cstddef>
#include <iostream>
// Asumsi tentang platform diperiksa compiler, bukan saat program berjalan.
static_assert(sizeof(int) >= 4, "program ini mengasumsikan int minimal 4 byte");
static_assert(sizeof(void*) == 8, "program ini hanya untuk arsitektur 64-bit");
int main() {
std::cout << "semua asumsi compile time terpenuhi\n";
std::cout << "sizeof(int) = " << sizeof(int) << " byte\n";
std::cout << "sizeof(void*) = " << sizeof(void*) << " byte\n";
return 0;
}Output yang Dihasilkan:
semua asumsi compile time terpenuhi
sizeof(int) = 4 byte
sizeof(void*) = 8 byteProgram ini dikompilasi dan berjalan karena kedua asumsi benar pada mesin 64-bit kita. Sekarang bandingkan bila asumsinya kita langgar — misalnya file asumsi-salah.cpp yang menuntut int berukuran 64 byte:
static_assert(sizeof(int) >= 64, "program ini mengasumsikan int minimal 64 byte");
int main() { return 0; }Hasil kompilasinya (eksekusi nyata):
asumsi-salah.cpp:1:27: error: static assertion failed: program ini mengasumsikan int minimal 64 byte
1 | static_assert(sizeof(int) >= 64, "program ini mengasumsikan int minimal 64 byte");
| ~~~~~~~~~~~~^~~~~
asumsi-salah.cpp:1:27: note: the comparison reduces to ‘(4 >= 64)’Tidak ada executable yang dihasilkan, dan pesannya menyebut persis asumsi mana yang gagal. Mesin yang melanggar asumsi itu tidak akan pernah berhasil menjalankan versi program yang salah — kesalahan berhenti di meja developer. Prinsip “geser ke kiri” inilah yang juga membedakan cara kita menangani kegagalan runtime: kita ingin pemeriksaan yang bisa dilakukan di awal dilakukan di awal, dan sisanya dilaporkan dengan mekanisme yang terstruktur.
Sekarang peta kegagalan kita lengkap: compile error tertangkap compiler, link error tertangkap linker, dan runtime error harus ditangani program sendiri. Untuk kategori ketiga inilah C++ menyediakan mekanisme pelaporan terstruktur yang paling dikenal — exception — topik utuh chapter berikutnya.
Referensi: cppreference — static_assert , GCC Online Docs — Invoking G++ .