Debugging ve Profiling Teknikleri
"Kod yazmak işin yarısı, hata bulmak diğer yarısı." Bu söz abartı değil. Deneyimli programcılar zamanlarının büyük bölümünü debugging'e harcar. Ama doğru araçları ve teknikleri bilirsen, bu süre dramatik şekilde azalır.
Debugging'i bir dedektiflik gibi düşün. Olay yeri: çöken program. İpuçları: hata mesajları, log'lar, bellek durumu. Araçların: derleyici uyarıları, debugger, sanitizer'lar. İyi bir dedektif rastgele aramaz — sistematik çalışır. Debugging de öyle.
Compiler Uyarıları — İlk Savunma Hattı
Debugging'in en ucuz ve en etkili yolu: derleyicinin sana söylediklerini dinlemek. Varsayılan olarak derleyiciler birçok uyarıyı göstermez. Bunları açıkça etkinleştirmen gerekir.
Temel Uyarı Bayrakları
# Minimum tavsiye edilen bayraklar
g++ -Wall -Wextra -Wpedantic -std=c++17 main.cpp -o main
# Daha katı: uyarilari hata olarak kabul et
g++ -Wall -Wextra -Wpedantic -Werror -std=c++17 main.cpp -o main| Bayrak | Ne Yapar |
|---|---|
-Wall | Yaygın uyarıları etkinleştirir (aslında "hepsini" değil) |
-Wextra | Ekstra uyarılar (kullanılmayan parametre, vs.) |
-Wpedantic | Standart dışı uzantıları uyarır |
-Werror | Tüm uyarıları hata olarak kabul eder (derleme durur) |
Uyarıların Yakaladığı Hatalar
#include <iostream>
int topla(int a, int b) {
int sonuc = a + b;
// return unutuldu! -Wall bunu yakalar
}
int main() {
int x; // baslatilmamis! -Wextra uyarir
if (x == 5) { // tanimsiz davranis
std::cout << "Bes!\n";
}
int y = 3;
if (y = 5) { // atama mi karsilastirma mi? -Wall uyarir
std::cout << "Bu her zaman calisir\n";
}
return 0;
}Bu kodda üç hata var ve derleyici uyarıları hepsini yakalar:
toplafonksiyonunda return yokxbaşlatılmamışif (y = 5)muhtemelenif (y == 5)olmalıydı
💡 Her zaman `-Wall -Wextra` ile derle. Yeni proje mi başlıyorsun?
-Werrorde ekle — uyarı birikmeden hepsini çözmek zorunda kalırsın. Bu disiplin uzun vadede çok zaman kazandırır.
assert() ile Kontrol
assert() makrosu, bir koşulun doğru olmasını beklediğin yerlere kontrol noktaları koyar. Koşul yanlışsa program durur ve sana tam olarak nerede durduğunu söyler.
#include <iostream>
#include <cassert>
double karekokHesapla(double x) {
assert(x >= 0.0 && "Negatif sayinin karekoku alinamaz!");
double tahmin = x / 2.0;
for (int i = 0; i < 100; i++) {
tahmin = (tahmin + x / tahmin) / 2.0;
}
return tahmin;
}
int main() {
std::cout << "sqrt(25) = " << karekokHesapla(25.0) << "\n";
std::cout << "sqrt(-4) = " << karekokHesapla(-4.0) << "\n"; // ASSERT FAIL!
return 0;
}Çıktı:
sqrt(25) = 5
program: main.cpp:5: double karekokHesapla(double): Assertion `x >= 0.0 && "Negatif sayinin karekoku alinamaz!"' failed.
AbortedDosya adı, satır numarası ve koşul — hata bulmak için yeterli bilgi.
assert vs exception
| Özellik | assert | exception |
|---|---|---|
| Amaç | Programcı hatası (bug) yakalama | Çalışma zamanı hatası yönetimi |
| Release'de | NDEBUG ile devre dışı | Her zaman aktif |
| Tepki | Program durur (abort) | Yakalanabilir, kurtarılabilir |
// assert — bu asla olmamali (programci hatasi)
assert(index < boyut && "Index sinir disi!");
// exception — bu olabilir (kullanici girdisi)
if (yas < 0) {
throw std::invalid_argument("Yas negatif olamaz");
}⚠️ assert içinde yan etkili ifadeler yazma!
assert(dosya.open("test.txt"))gibi bir ifade Release modda devre dışı kalır ve dosya hiç açılmaz. Yan etkili işlemleri assert dışında yap.
GDB — GNU Debugger Temelleri
GDB, C++ için standart komut satırı debugger'ıdır. Programı adım adım çalıştırmanı, değişkenleri incelemeni ve çökme noktasını bulmanı sağlar.
Hazırlık
Debug bilgisiyle derle:
g++ -g -O0 main.cpp -o program
# -g: debug bilgisi ekle
# -O0: optimizasyon kapali (degiskenler optimize edilmesin)Temel GDB Komutları
gdb ./program # GDB'yi baslatGDB içinde:
| Komut | Kısaltma | Açıklama |
|---|---|---|
break main | b main | main fonksiyonunda durma noktası koy |
break main.cpp:15 | b main.cpp:15 | 15. satırda durma noktası |
run | r | Programı başlat |
next | n | Bir sonraki satıra geç (fonksiyona girme) |
step | s | Bir sonraki satıra geç (fonksiyona gir) |
continue | c | Sonraki breakpoint'e kadar devam et |
print x | p x | x değişkeninin değerini göster |
backtrace | bt | Çağrı yığınını (call stack) göster |
info locals | Yerel değişkenleri listele | |
quit | q | GDB'den çık |
Pratik Örnek
Diyelim ki şu program çöküyor:
#include <iostream>
#include <vector>
int elemanBul(const std::vector<int>& v, int hedef) {
for (size_t i = 0; i <= v.size(); i++) { // BUG: <= yerine < olmali
if (v[i] == hedef) {
return i;
}
}
return -1;
}
int main() {
std::vector<int> sayilar = {10, 20, 30, 40, 50};
int sonuc = elemanBul(sayilar, 99);
std::cout << "Sonuc: " << sonuc << "\n";
return 0;
}GDB oturumu:
$ gdb ./program
(gdb) break elemanBul
(gdb) run
Breakpoint 1, elemanBul at main.cpp:4
(gdb) print v.size()
$1 = 5
(gdb) next
(gdb) next
(gdb) print i
$2 = 0
(gdb) continue
... (program coker)
(gdb) backtrace
#0 elemanBul at main.cpp:5
#1 main at main.cpp:13
(gdb) print i
$3 = 5 <-- i == 5, ama v.size() == 5, yani v[5] sinir disi!backtrace komutu çökmenin nerede olduğunu gösterir. i = 5 iken v[5]'e erişim — sınır dışı.
💡 IDE'lerin debugger'ları GDB'yi grafiksel arayüzle sarar. VS Code, CLion, Qt Creator — hepsi arka planda GDB veya LLDB kullanır. Ama terminal GDB bilmek sorun çözme gücünü artırır.
AddressSanitizer (ASan)
AddressSanitizer, bellek hatalarını çalışma zamanında tespit eden bir derleyici aracıdır. Programın 2x kadar yavaşlar ama bellek hatalarını kesin olarak bulur.
Etkinleştirme
g++ -g -fsanitize=address -fno-omit-frame-pointer main.cpp -o program
./programASan'ın Yakaladığı Hatalar
1. Buffer Overflow:
int main() {
int dizi[5] = {1, 2, 3, 4, 5};
dizi[5] = 6; // STACK BUFFER OVERFLOW — ASan yakalar
return 0;
}ASan çıktısı:
ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fff...
WRITE of size 4 at 0x7fff... thread T0
#0 main main.cpp:32. Use After Free:
int main() {
int* p = new int(42);
delete p;
*p = 10; // USE AFTER FREE — ASan yakalar
return 0;
}3. Memory Leak:
int main() {
int* p = new int(42);
// delete p; — unutuldu
return 0;
}
// ASan: detected memory leaksASan hatanın tam yerini (dosya, satır), erişim türünü (read/write) ve bellek durumunu gösterir.
UndefinedBehaviorSanitizer (UBSan)
UBSan, tanımsız davranış (undefined behavior) olan kodları tespit eder. ASan ile birlikte kullanılabilir.
g++ -g -fsanitize=undefined main.cpp -o program
./program
# ASan + UBSan birlikte
g++ -g -fsanitize=address,undefined main.cpp -o programUBSan'ın Yakaladığı Hatalar
#include <iostream>
#include <climits>
int main() {
// Signed integer overflow — tanimsiz davranis!
int x = INT_MAX;
x = x + 1; // UBSan yakalar
std::cout << x << "\n";
// Sifira bolme
int a = 10, b = 0;
int c = a / b; // UBSan yakalar
// Null pointer dereference
int* p = nullptr;
*p = 5; // UBSan yakalar
return 0;
}UBSan çıktısı:
main.cpp:7:9: runtime error: signed integer overflow: 2147483647 + 1
cannot be represented in type 'int'⚠️ CI/CD pipeline'ında her iki sanitizer'ı da çalıştır. Debug build'lerde ASan + UBSan aktif olsun. Release build'lerde kapalı olsun (performans maliyeti var).
Logging Stratejileri
Debugger her zaman kullanılabilir olmayabilir (üretim ortamı, uzak sunucu, aralıklı hata). Logging bu durumlar için vazgeçilmezdir.
Basit Log Sistemi
#include <iostream>
#include <fstream>
#include <string>
#include <chrono>
#include <ctime>
enum class LogSeviye {
DEBUG, INFO, UYARI, HATA
};
class Logger {
public:
Logger(const std::string& dosyaAdi)
: dosya_(dosyaAdi, std::ios::app) {}
void log(LogSeviye seviye, const std::string& mesaj) {
auto simdi = std::chrono::system_clock::now();
auto zaman = std::chrono::system_clock::to_time_t(simdi);
dosya_ << zamanDamgasi(zaman) << " ["
<< seviyeStr(seviye) << "] "
<< mesaj << "\n";
dosya_.flush();
}
private:
std::ofstream dosya_;
std::string seviyeStr(LogSeviye s) {
switch (s) {
case LogSeviye::DEBUG: return "DEBUG";
case LogSeviye::INFO: return "INFO ";
case LogSeviye::UYARI: return "UYARI";
case LogSeviye::HATA: return "HATA ";
}
return "?????";
}
std::string zamanDamgasi(std::time_t t) {
char buf[20];
std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", std::localtime(&t));
return buf;
}
};
int main() {
Logger logger("uygulama.log");
logger.log(LogSeviye::INFO, "Uygulama basladi");
logger.log(LogSeviye::DEBUG, "Veritabanina baglaniliyor...");
logger.log(LogSeviye::UYARI, "Baglanti yavas");
logger.log(LogSeviye::HATA, "Sorgu basarisiz: timeout");
logger.log(LogSeviye::INFO, "Uygulama kapandi");
return 0;
}Log dosyası çıktısı:
2026-02-19 23:50:00 [INFO ] Uygulama basladi
2026-02-19 23:50:00 [DEBUG] Veritabanina baglaniliyor...
2026-02-19 23:50:01 [UYARI] Baglanti yavas
2026-02-19 23:50:02 [HATA ] Sorgu basarisiz: timeout
2026-02-19 23:50:02 [INFO ] Uygulama kapandiLog Seviyeleri Ne Zaman Kullanılır?
| Seviye | Kullanım |
|---|---|
| DEBUG | Geliştirme sırasında detaylı bilgi |
| INFO | Normal çalışma bilgisi |
| UYARI | Potansiyel sorun ama program çalışmaya devam ediyor |
| HATA | Bir işlem başarısız oldu |
💡 Üretim ortamında log seviyesini filtrele. DEBUG log'ları geliştirme ortamında açık, üretimde kapalı olsun. Çok fazla log da performans ve disk sorunlarına yol açar.
Common Bug Pattern'lar ve Çözümleri
1. Off-by-One Hatası
Döngü sınırlarında bir fazla veya bir eksik iterasyon.
// HATALI
for (int i = 0; i <= n; i++) { ... } // n+1 iterasyon
// DOGRU
for (int i = 0; i < n; i++) { ... } // n iterasyonÇözüm: Range-based for loop kullan, mümkünse indeks kullanma.
2. Başlatılmamış Değişken
// HATALI
int toplam;
for (int i = 0; i < 10; i++) {
toplam += i; // toplam baslangicta ne?
}
// DOGRU
int toplam = 0;Çözüm: Değişkenleri tanımlandığı yerde başlat. {} kullan.
3. Iterator Invalidation
Container'ı değiştirirken iterator kullanmak.
// HATALI
std::vector<int> v = {1, 2, 3, 4, 5};
for (auto it = v.begin(); it != v.end(); ++it) {
if (*it % 2 == 0) {
v.erase(it); // iterator gecersiz oldu!
}
}
// DOGRU — erase-remove idiom
v.erase(std::remove_if(v.begin(), v.end(),
[](int x) { return x % 2 == 0; }), v.end());4. Dangling Reference
Ömrü biten nesneye referans tutmak.
// HATALI
const std::string& tehlike() {
std::string gecici = "merhaba";
return gecici; // gecici yok olacak!
}
// DOGRU — deger olarak don
std::string guvenli() {
std::string gecici = "merhaba";
return gecici; // move semantics ile verimli
}5. Integer Overflow
// HATALI
int faktoriyel(int n) {
int sonuc = 1;
for (int i = 2; i <= n; i++) {
sonuc *= i; // 13! icin int tasar!
}
return sonuc;
}
// DOGRU
long long faktoriyel(int n) {
long long sonuc = 1;
for (int i = 2; i <= n; i++) {
sonuc *= i;
}
return sonuc;
}Çözüm: Büyük sayılarla çalışırken long long kullan. UBSan ile signed overflow'u tespit et.
Debugging Kontrol Listesi
Bir hata bulduğunda sırayla dene:
Derleyici uyarılarını aç (
-Wall -Wextra) — çoğu hata burada yakalanırSanitizer'larla çalıştır (
-fsanitize=address,undefined) — bellek ve UB hatalarıHatayı tekrarla — tutarlı tekrarlama yoksa debug edemezsin
Minimal örnek oluştur — hatayı en az kodla tekrarla
GDB ile adım adım ilerle — değişken değerlerini kontrol et
Log ekle — aralıklı hatalar için log'lar hayat kurtarır
Rubber duck debugging — sorunu birine (veya bir ördek figürüne) anlatırken çoğu zaman cevabı kendin bulursun
Özet
Derleyici uyarıları (
-Wall -Wextra -Werror) en ucuz ve en etkili debugging aracıdır. Her zaman etkinleştir.assert() ile programcı hatalarını (bug) yakala. Koşul yanlışsa program durur ve hatanın yerini söyler. Release'de devre dışı kalır.
GDB ile programı adım adım çalıştır:
break,run,next,print,backtracetemel komutlardır. Çökme noktasını ve değişken değerlerini inceleme imkanı verir.AddressSanitizer bellek hatalarını (buffer overflow, use-after-free, leak), UndefinedBehaviorSanitizer tanımsız davranışları (integer overflow, null deref) çalışma zamanında tespit eder.
Logging ile uzak sunucularda ve aralıklı hatalarda bilgi topla. Seviye bazlı (DEBUG/INFO/UYARI/HATA) log sistemi kur.
Yaygın bug pattern'lar: off-by-one, başlatılmamış değişken, iterator invalidation, dangling reference, integer overflow. Hepsinin çözümü modern C++ araçlarını (range-for, RAII, smart pointer) kullanmaktır.
AI Asistan
Sorularını yanıtlamaya hazır