Berhenti di baris mana pun sudah cukup untuk bug sederhana, tetapi program nyata bisa menjalankan loop ribuan kali sebelum kejadian yang kita cari. Berhenti di setiap iterasi berarti tangan kita mati sebelum menemukan apa pun. gdb menjawab kebutuhan itu dengan dua mekanisme: breakpoint bersyarat yang hanya aktif bila ekspresi tertentu benar, dan watchpoint yang menaruh penjaga pada sebuah variabel — program berhenti otomatis di baris mana pun yang mengubah nilai variabel tersebut, bahkan kalau kita tidak tahu baris itu ada. Chapter ini memakai program dengan bug efek samping untuk melatih keduanya.

Program Target: Total yang Berkurang Sendiri #

Program laporan berikut mengumpulkan total penjualan, tetapi hasil akhirnya lebih kecil dari seharusnya. Kita tahu nilai akhirnya salah, tetapi tidak tahu baris mana yang bersalah:

cpp
// laporan-penjualan.cpp
// Target latihan chapter breakpoints-dan-watchpoints:
// totalPenjualan berakhir bernilai aneh, dan kita tidak tahu baris mana
// yang mengubahnya — pekerjaan klasik untuk watchpoint.

#include <iostream>
#include <string>
#include <utility>
#include <vector>

long totalPenjualan = 0;

void catatBarang(int harga) {
    totalPenjualan += harga;
}

// "Menyegarkan" buffer tampilan laporan — seharusnya tidak menyentuh total.
void segarkanTampilan() {
    totalPenjualan = 0;
}

void prosesLaporan(const std::vector<std::pair<std::string, int>>& barang) {
    for (const auto& b : barang) {
        catatBarang(b.second);
        if (b.first == "kabel data") {
            segarkanTampilan();   // efek samping tersembunyi ada di sini
        }
    }
}

int main() {
    std::vector<std::pair<std::string, int>> barang = {
        {"mouse", 120000},
        {"kabel data", 45000},
        {"keyboard", 310000},
        {"monitor", 1850000},
    };

    prosesLaporan(barang);
    std::cout << "total penjualan: " << totalPenjualan << "\n";
    return 0;
}

Output yang Dihasilkan:

text
total penjualan: 2160000

Nilai yang benar adalah 2325000 (jumlah keempat barang). Output memunculkan 2160000 — selisih 165000, persis total dua barang pertama sebelum nilainya ditimpa. Dari angka itu kita menduga ada baris yang menulis ulang totalPenjualan dari nol, tetapi mencari penulisnya dengan mata saja melelahkan: variabel ini disentuh di tiga function berbeda. Dengan printf kita bisa menyisipkan pencetak di ketiganya; dengan gdb kita cukup menaruh satu watchpoint.

Breakpoint Bersyarat: Berhenti Hanya Bila Cocok #

Bentuk umumnya break lokasi if ekspresi. gdb mengevaluasi ekspresi setiap kali eksekusi melintasi lokasi itu dan hanya berhenti bila hasilnya benar. Tampilan berikut mengilustrasikan penggunaannya (angka alamat dan detail tampilan bisa berbeda antar mesin):

text Contoh tampilan breakpoint bersyarat (ilustrasi)
(gdb) break 24 if b.second == 45000
Breakpoint 1 at 0x11e2: file laporan-penjualan.cpp, line 24.
(gdb) run
Starting program: /home/budi/latihan/laporan-penjualan

Breakpoint 1, prosesLaporan (barang=std::vector of length 4, capacity 4)
    at laporan-penjualan.cpp:24
24	        catatBarang(b.second);
(gdb) print b
$1 = {"kabel data", 45000}

Program melewati baris 24 untuk keempat barang, tetapi hanya berhenti pada barang yang harganya 45000. Pola ini menyelamatkan kita saat bug hanya muncul pada satu input di antara ribuan: berhenti if nama == "kabel data", if id > 9000, atau if saldo < 0. Ekspresinya boleh memakai variabel apa pun yang terlihat di frame tersebut, dan kalau syarat terlalu ketat (tidak pernah benar), gdb tetap jalan — program hanya tidak pernah berhenti, jadi periksa ejaan ekspresi bila breakpoint tampak “tidak menyala”.

Watchpoint: Menjaga Sebuah Variabel #

Watchpoint menjawab pertanyaan “kapan dan di mana nilai ini berubah?” tanpa perlu menebak lokasi. Alurnya: berhenti dulu di main agar variabel sudah dikenal, pasang watch, lalu biarkan program berjalan:

text Contoh tampilan sesi watchpoint (ilustrasi)
(gdb) break main
Breakpoint 1 at 0x11d9: file laporan-penjualan.cpp, line 32.
(gdb) run
Starting program: /home/budi/latihan/laporan-penjualan

Breakpoint 1, main () at laporan-penjualan.cpp:32
32	    std::vector<std::pair<std::string, int>> barang = {
(gdb) watch totalPenjualan
Hardware watchpoint 2: totalPenjualan
(gdb) continue
Continuing.
Hardware watchpoint 2: totalPenjualan

Old value = 0
New value = 120000
catatBarang (harga=120000) at laporan-penjualan.cpp:14
14	    totalPenjualan += harga;
(gdb) continue
Continuing.
Hardware watchpoint 2: totalPenjualan

Old value = 120000
New value = 165000
catatBarang (harga=45000) at laporan-penjualan.cpp:14
14	    totalPenjualan += harga;
(gdb) continue
Continuing.
Hardware watchpoint 2: totalPenjualan

Old value = 165000
New value = 0
0x00005555555551b7 in segarkanTampilan () at laporan-penjualan.cpp:19
19	    totalPenjualan = 0;
(gdb) backtrace
#0  0x00005555555551b7 in segarkanTampilan () at laporan-penjualan.cpp:19
#1  0x0000555555555231 in prosesLaporan (barang=...) at laporan-penjualan.cpp:26
#2  0x00005555555552b0 in main () at laporan-penjualan.cpp:39

Baca laporan itu seperti berita kriminal:

  1. Dua penghentian pertama adalah penulisan yang wajar — catatBarang menambah 120000 lalu 45000. Kita melompatinya dengan continue.
  2. Penghentian ketiga menunjukkan Old value = 165000, New value = 0 — total anjlok, dan gdb menunjuk baris pelakunya: totalPenjualan = 0; di segarkanTampilan().
  3. backtrace sesudahnya melengkapi motif: baris 19 dipanggil dari baris 26 di prosesLaporan, yang terpicu nama barang "kabel data". Bug terungkap lengkap dengan rantai panggilannya — tanpa satu baris printf pun.

Ada tiga ragam: watch berhenti saat nilai dibaca maupun ditulis (aktual: saat ditulis), rwatch berhenti saat dibaca, dan awatch berhenti saat dibaca atau ditulis. Label Hardware watchpoint berasal dari register debug CPU yang memantau alamat memori secara gratis; bila register habis (jumlahnya terbatas), gdb beralih ke watchpoint perangkat lunak yang menelusuri program langkah demi langkah dan jauh lebih lambat — memantau satu variabel penting saja, bukan sepuluh sekaligus.

Strategi Menempatkan Breakpoint #

Breakpoint yang tepat memangkas panjang sesi debugging. Aturan yang terbukti berguna:

  • Berhenti sedekat mungkin ke titik kejanggalan teramati: bila output baris ke-40 sudah salah dan baris ke-39 masih benar, breakpoint di sekitar baris 40 lebih produktif daripada di awal program.
  • Breakpoint di nama function (break segarkanTampilan) untuk memeriksa “apakah dan kapan function ini dipanggil”, lalu tambahkan syarat bila panggilannya banyak.
  • catch throw menaruh breakpoint pada setiap throw — sahabat utama saat exception dilempar dari tempat yang tidak terduga; gdb berhenti tepat sebelum exception terbang.
  • tbreak membuat breakpoint sekali pakai yang menghapus dirinya setelah tercapai — cocok untuk pemberhentian “sekadar melintasi titik ini”.
  • Jangan menaruh breakpoint di dalam header library standar; berhentilah di kode Anda sendiri dan gunakan finish/next untuk menutupi pemanggilan yang sudah dipercaya.

Sekarang kita mampu berhenti di titik yang tepat dan membaca identitas pelakunya. Chapter berikutnya melangkah lebih jauh: membaca stack trace — laporan bertingkat yang muncul saat program berhenti, melempar exception, atau benar-benar runtuh — dan cara menerjemahkannya menjadi lokasi bug.

Referensi: GCC — Debugging Options (-g) .