← Kursa Dön
📄 Text · 18 min

Undefined Behavior ve Memory Safety

Bir uçağın otopilot yazılımında küçük bir bug olduğunu düşün. Normal koşullarda sorunsuz çalışıyor — testlerden geçiyor, simülasyonlarda mükemmel sonuç veriyor. Ama belirli bir irtifada, belirli bir rüzgar açısıyla, belirli bir sıcaklıkta program beklenmedik bir şey yapıyor. Bazen hiçbir şey olmuyor. Bazen uçak hafifçe sallanıyor. Bazen... düşüyor. Ve en kötüsü: aynı koşullarda her seferinde farklı davranıyor.

İşte Undefined Behavior (UB) tam olarak bu. C++ standardının "bu durumda ne olacağını tanımlamıyorum, derleyici ve donanım ne isterse yapabilir" dediği durumlar. Ve C++ bu durumlarla dolu. Bu derste en yaygın UB'leri öğrenecek, her birinin neden tehlikeli olduğunu anlayacak, nasıl tespit edileceğini ve nasıl önleneceğini göreceğiz.


Undefined Behavior Nedir?

Standardın Tanımı

C++ standardı üç davranış kategorisi tanımlar:

KategoriTanımÖrnek
DefinedDavranış tam olarak belirliint x = 5 + 3; → her zaman 8
Implementation-definedDerleyiciye bağlı ama tutarlısizeof(int) → 4 veya 8, ama tutarlı
UndefinedHiçbir garanti yokSigned integer overflow, null dereference

UB olan bir programda her şey olabilir:

  • Beklediğin gibi çalışabilir (en tehlikelisi — bug'ı gizler)

  • Çökebilir (en iyisi — en azından fark edersin)

  • Farklı sonuçlar verebilir (her çalıştırmada farklı)

  • Güvenlik açığı oluşturabilir (saldırgan exploit edebilir)

  • Hard disk'i formatlayabilir (teorik ama standart izin veriyor)

Derleyici Optimizasyonları ve UB

UB'nin gerçek tehlikesi şurada: derleyici, UB olmayacağını varsayarak optimizasyon yapar. Bu da beklenmedik sonuçlara yol açar.

#include <iostream>

bool kontrolEt(int* ptr) {
    int deger = *ptr;       // UB eğer ptr null ise
    if (ptr == nullptr) {   // Derleyici: "ptr null olamaz,
        return false;       //  çünkü üst satırda zaten dereference ettim"
    }                       // Bu if tamamen kaldırılabilir!
    return deger > 0;
}

Derleyici düşüncesi: "Programcı *ptr yazdıysa, ptr null değildir (çünkü null olsa UB olurdu ve ben UB olmayacağını varsayıyorum). O zaman ptr == nullptr kontrolü her zaman false — kaldırayım." Bu optimizasyon tamamen legal ve gerçek derleyiciler bunu yapıyor.


En Yaygın Undefined Behavior'lar

1. Buffer Overflow — Sınır Dışı Erişim

Kötü kod:

#include <iostream>

void bufferOverflow() {
    int dizi[5] = {10, 20, 30, 40, 50};
    
    // Sınır dışı okuma — UB
    std::cout << dizi[5] << std::endl;   // 5. index yok (0-4 arası geçerli)
    std::cout << dizi[100] << std::endl; // Tamamen rastgele bellek
    
    // Sınır dışı yazma — çok daha tehlikeli
    dizi[5] = 999;    // Stack'teki başka değişkenleri ezebilir
    dizi[-1] = 888;   // Negatif index de UB
}

Neden UB: C dizileri (ve std::array) sınır kontrolü yapmaz. Stack üzerindeki diğer değişkenleri, return adresini, hatta fonksiyon pointer'larını ezebilirsin. Bu, tarihte en çok exploit edilen güvenlik açığıdır.

Düzeltilmiş:

#include <iostream>
#include <vector>
#include <array>

void guvenliErisim() {
    std::vector<int> vec = {10, 20, 30, 40, 50};
    
    // at() sınır kontrolü yapar — out_of_range exception fırlatır
    try {
        std::cout << vec.at(5) << std::endl;
    } catch (const std::out_of_range& e) {
        std::cout << "Sinir disi: " << e.what() << std::endl;
    }
    
    // veya index kontrol et
    size_t index = 3;
    if (index < vec.size()) {
        std::cout << vec[index] << std::endl;  // Güvenli
    }
    
    // std::array ile de at() kullanılabilir
    std::array<int, 5> arr = {10, 20, 30, 40, 50};
    std::cout << arr.at(2) << std::endl;  // 30, güvenli
}

2. Dangling Pointer — Geçersiz Pointer

Kötü kod:

#include <iostream>

int* tehlikeliFonksiyon() {
    int yerelDeger = 42;
    return &yerelDeger;  // UB: yerel değişkenin adresini döndürme
}  // yerelDeger burada yok oldu — pointer artık geçersiz

void danglingPointer() {
    int* ptr = tehlikeliFonksiyon();
    std::cout << *ptr << std::endl;  // UB: dangling pointer dereference
    // Bazen 42 yazabilir (bellek henüz üzerine yazılmadı)
    // Bazen çöp değer yazabilir
    // Bazen segfault verir
}

Neden UB: yerelDeger fonksiyon bitince stack'ten kaldırılır. Pointer hâlâ o adresi gösterir ama orası artık geçerli bir nesne değildir. Bir sonraki fonksiyon çağrısı o belleği üzerine yazar.

Düzeltilmiş:

#include <iostream>
#include <memory>

// Yol 1: Değer döndür (RVO sayesinde verimli)
int guvenliDeger() {
    int deger = 42;
    return deger;  // Kopya — güvenli
}

// Yol 2: Smart pointer kullan
std::unique_ptr<int> guvenliPointer() {
    auto ptr = std::make_unique<int>(42);
    return ptr;  // Sahiplik transfer — güvenli
}

// Yol 3: Heap'te oluştur ve sahipliği aktar
std::shared_ptr<int> paylasilanPointer() {
    return std::make_shared<int>(42);
}

3. Use-After-Free

Kötü kod:

#include <iostream>

void useAfterFree() {
    int* ptr = new int(100);
    std::cout << *ptr << std::endl;  // OK: 100
    
    delete ptr;  // Bellek serbest bırakıldı
    
    // UB: Serbest bırakılmış belleğe erişim
    std::cout << *ptr << std::endl;  // Çöp değer, çökme veya eski değer
    *ptr = 200;                       // Başkasının belleğine yazıyor olabilirsin
}

Neden UB: delete sonrası bellek allocator'a geri verilir. O alan başka bir new tarafından kullanılabilir. Oraya yazmak başka nesnenin verisini bozar — debug etmesi neredeyse imkansız hatalar yaratır.

Düzeltilmiş:

#include <iostream>
#include <memory>

void guvenliKullanim() {
    // unique_ptr ile — delete gerekmiyor, use-after-free imkansız
    auto ptr = std::make_unique<int>(100);
    std::cout << *ptr << std::endl;
    
    ptr.reset();  // Serbest bırakıldı
    
    if (ptr) {  // nullptr kontrolü — güvenli
        std::cout << *ptr << std::endl;
    } else {
        std::cout << "Pointer bos, erisim yok" << std::endl;
    }
}

4. Double Free

Kötü kod:

void doubleFree() {
    int* ptr = new int(42);
    delete ptr;
    delete ptr;  // UB: aynı bellek iki kez serbest bırakılıyor
}

void aliasDoubleFree() {
    int* a = new int(10);
    int* b = a;     // İki pointer aynı yeri gösteriyor
    delete a;
    delete b;       // UB: zaten serbest bırakıldı
}

Neden UB: Allocator'ın iç veri yapılarını bozar. Heap corruption'a yol açar — sonraki new çağrıları beklenmedik davranabilir, program çok sonra başka bir yerde çökebilir.

Düzeltilmiş:

#include <memory>

void guvenliSahiplik() {
    // unique_ptr — tek sahip, otomatik delete, double free imkansız
    auto ptr = std::make_unique<int>(42);
    // Scope sonunda otomatik delete — elle delete yok
    
    // Paylaşılan sahiplik gerekiyorsa
    auto shared1 = std::make_shared<int>(10);
    auto shared2 = shared1;  // Reference count: 2
    // Her ikisi de scope'tan çıkınca count 0 olur → tek delete
}

5. Signed Integer Overflow

Kötü kod:

#include <iostream>
#include <climits>

void signedOverflow() {
    int x = INT_MAX;         // 2,147,483,647
    int y = x + 1;           // UB! Signed overflow tanımsız
    std::cout << y << std::endl;  // -2,147,483,648 olabilir, 0 olabilir, çökebilir
    
    // Döngülerde de tehlikeli
    for (int i = 0; i <= INT_MAX; ++i) {  // Sonsuz döngü olabilir!
        // Derleyici: "i her zaman <= INT_MAX (çünkü overflow UB),
        // o zaman koşul her zaman true — döngüyü optimize edeyim"
    }
}

Neden UB: C++ standardı signed integer overflow'u tanımlamaz. Derleyici, overflow olmayacağını varsayarak agresif optimizasyonlar yapar. unsigned overflow ise tanımlıdır (modüler aritmetik, wrap-around).

Düzeltilmiş:

#include <iostream>
#include <climits>
#include <cstdint>
#include <stdexcept>

// Güvenli toplama — overflow kontrolü
int guvenliTopla(int a, int b) {
    if (b > 0 && a > INT_MAX - b) {
        throw std::overflow_error("Toplama overflow");
    }
    if (b < 0 && a < INT_MIN - b) {
        throw std::overflow_error("Toplama underflow");
    }
    return a + b;
}

// Alternatif: unsigned veya daha geniş tip kullan
void guvenliDongu() {
    // size_t (unsigned) kullan, overflow tanımlı
    for (size_t i = 0; i < 1000000; ++i) {
        // Güvenli
    }
    
    // Veya büyük tip kullan
    int64_t buyukSayi = static_cast<int64_t>(INT_MAX) + 1;  // Güvenli
}

6. Null Pointer Dereference

Kötü kod:

#include <iostream>

struct Kullanici {
    std::string isim;
    int yas;
};

void nullDereference() {
    Kullanici* kul = nullptr;
    std::cout << kul->isim << std::endl;  // UB: null pointer dereference
    
    int* ptr = nullptr;
    *ptr = 42;  // UB: null'a yazma
}

Düzeltilmiş:

#include <iostream>
#include <optional>
#include <memory>

// Yol 1: nullptr kontrolü
void nullKontrol(Kullanici* kul) {
    if (kul != nullptr) {
        std::cout << kul->isim << std::endl;
    }
}

// Yol 2: Referans kullan (null olamaz)
void referansKullan(const Kullanici& kul) {
    std::cout << kul.isim << std::endl;  // Referans null olamaz
}

// Yol 3: std::optional (değer yokluğunu güvenle ifade et)
std::optional<Kullanici> kullaniciBul(int id) {
    if (id == 1) return Kullanici{"Tolga", 25};
    return std::nullopt;  // nullptr yerine optional
}

void optionalKullan() {
    auto sonuc = kullaniciBul(99);
    if (sonuc.has_value()) {
        std::cout << sonuc->isim << std::endl;
    } else {
        std::cout << "Kullanici bulunamadi" << std::endl;
    }
}

7. Data Race — Yarış Durumu

Kötü kod:

#include <iostream>
#include <thread>

int sayac = 0;  // Paylaşılan değişken — korumasız

void dataRace() {
    auto artir = []() {
        for (int i = 0; i < 100000; ++i) {
            sayac++;  // UB: aynı anda iki thread yazıyor
        }
    };
    
    std::thread t1(artir);
    std::thread t2(artir);
    t1.join();
    t2.join();
    
    // Beklenen: 200000 — Gerçek: ? (her seferinde farklı)
    std::cout << "Sayac: " << sayac << std::endl;
}

Neden UB: İki thread aynı anda aynı belleğe yazıyorsa ve en az biri yazma işlemiyse, bu data race'dir ve UB'dir. Sonuç sadece yanlış değil, tanımsızdır — program çökebilir, bellek bozulabilir.

Düzeltilmiş:

#include <iostream>
#include <thread>
#include <atomic>
#include <mutex>

// Yol 1: atomic — lock-free senkronizasyon
std::atomic<int> atomikSayac{0};

void atomikArtir() {
    auto artir = []() {
        for (int i = 0; i < 100000; ++i) {
            atomikSayac.fetch_add(1, std::memory_order_relaxed);
        }
    };
    
    std::thread t1(artir);
    std::thread t2(artir);
    t1.join();
    t2.join();
    std::cout << "Sayac: " << atomikSayac << std::endl;  // Her zaman 200000
}

// Yol 2: mutex — lock-based senkronizasyon
int normalSayac = 0;
std::mutex mtx;

void mutexArtir() {
    auto artir = []() {
        for (int i = 0; i < 100000; ++i) {
            std::lock_guard<std::mutex> kilit(mtx);
            normalSayac++;  // Mutex koruması altında — güvenli
        }
    };
    
    std::thread t1(artir);
    std::thread t2(artir);
    t1.join();
    t2.join();
    std::cout << "Sayac: " << normalSayac << std::endl;  // Her zaman 200000
}

Sanitizer'lar: UB Dedektörleri

Manuel code review ile tüm UB'leri bulmak pratik olarak imkansızdır. Sanitizer'lar derleyicinin koda eklediği çalışma zamanı kontrollerdir. Programı biraz yavaşlatırlar ama UB'yi anında yakalayıp detaylı rapor verirler.

AddressSanitizer (ASan)

Bellek hatalarını yakalar: buffer overflow, use-after-free, double free, stack buffer overflow, memory leak.

# Derleme
g++ -fsanitize=address -g -O1 program.cpp -o program

# Çalıştırma
./program

ASan bir hata bulduğunda şöyle bir çıktı verir:

=================================================================
==12345==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000010
READ of size 4 at 0x602000000010 thread T0
    #0 0x4006b1 in main /home/user/program.cpp:8
    #1 0x7f... in __libc_start_main

0x602000000010 is located 0 bytes inside of 4-byte region [0x602000000010,0x602000000014)
freed by thread T0 here:
    #0 0x7f... in operator delete(void*)
    #1 0x400691 in main /home/user/program.cpp:6

previously allocated by thread T0 here:
    #0 0x7f... in operator new(unsigned long)
    #1 0x400671 in main /home/user/program.cpp:4

Bu rapor sana şunları söylüyor: hata ne (use-after-free), nerede oluştu (program.cpp satır 8), bellek ne zaman serbest bırakıldı (satır 6) ve ne zaman tahsis edildi (satır 4).

UndefinedBehaviorSanitizer (UBSan)

Tanımsız davranışları yakalar: signed overflow, null dereference, alignment violation, shift overflow.

g++ -fsanitize=undefined -g program.cpp -o program
./program
program.cpp:5:15: runtime error: signed integer overflow: 
2147483647 + 1 cannot be represented in type 'int'

ThreadSanitizer (TSan)

Data race'leri yakalar.

g++ -fsanitize=thread -g program.cpp -o program -pthread
./program
WARNING: ThreadSanitizer: data race (pid=12345)
  Write of size 4 at 0x... by thread T2:
    #0 main::$_0::operator()() program.cpp:7
  Previous write of size 4 at 0x... by thread T1:
    #0 main::$_0::operator()() program.cpp:7

MemorySanitizer (MSan)

Başlatılmamış bellek okumalarını yakalar. Sadece Clang'da mevcuttur.

clang++ -fsanitize=memory -g program.cpp -o program
./program

Sanitizer'ları Birlikte Kullanma

# ASan + UBSan birlikte (en yaygın kombinasyon)
g++ -fsanitize=address,undefined -g -O1 -fno-omit-frame-pointer program.cpp -o program

# CMake ile
cmake -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined -g" ..

⚠️ Önemli: ASan ve TSan birlikte kullanılamaz — farklı build'lerde çalıştır. ASan+UBSan bir build, TSan ayrı bir build.

CMake Entegrasyonu

# Debug build'de sanitizer'ları aktifleştir
option(ENABLE_SANITIZERS "Enable ASan + UBSan" OFF)

if(ENABLE_SANITIZERS)
    add_compile_options(-fsanitize=address,undefined -fno-omit-frame-pointer)
    add_link_options(-fsanitize=address,undefined)
endif()
cmake -B build -DENABLE_SANITIZERS=ON -DCMAKE_BUILD_TYPE=Debug ..
cmake --build build
./build/program

Valgrind: Memory Leak Detection

Valgrind, programı bir sanal makine içinde çalıştırarak bellek hatalarını tespit eder. Sanitizer'lardan daha yavaştır (10-50x) ama yeniden derleme gerektirmez.

# Derleme (debug sembollerle)
g++ -g program.cpp -o program

# Valgrind ile çalıştır
valgrind --leak-check=full --show-leak-kinds=all ./program

Çıktı:

==12345== HEAP SUMMARY:
==12345==     in use at exit: 400 bytes in 1 blocks
==12345==   total heap usage: 3 allocs, 2 frees, 73,104 bytes allocated
==12345== 
==12345== 400 bytes in 1 blocks are definitely lost in loss record 1 of 1
==12345==    at 0x4C2E: operator new[](unsigned long)
==12345==    by 0x400A: main (program.cpp:5)

"definitely lost" = kesin sızıntı, "possibly lost" = olası sızıntı (genelde de gerçek sızıntı).

Valgrind vs ASan karşılaştırma:

ÖzellikASanValgrind
Hız2x yavaşlatma10-50x yavaşlatma
Yeniden derlemeGerekliGereksiz
KapsamBuffer overflow, UAF, leakLeak, uninit read, genel bellek
PlatformGCC, ClangLinux, macOS

Pratikte: geliştirme sırasında ASan, CI/CD'de her ikisi, production'a yakın testlerde Valgrind kullan.


Safe Coding Guidelines

UB'den korunmanın en iyi yolu, ilk başta UB yazmamaktır. İşte temel kurallar:

1. Her Zaman Başlat (Initialize)

// ❌ Başlatılmamış değişken
int x;               // Belirsiz değer — okumak UB
int* ptr;             // Belirsiz pointer — dereference UB

// ✅ Her zaman başlat
int x = 0;
int* ptr = nullptr;
std::string s;        // Default constructor çağrılır — boş string

// C++11 brace initialization
int dizi[5] = {};     // Tüm elemanlar 0

2. Smart Pointer Kullan

// ❌ Raw pointer ile manuel yönetim
int* p = new int(42);
// ... 100 satır sonra ...
delete p;  // Unutulabilir, double free olabilir

// ✅ Smart pointer — kaynak yönetimi otomatik
auto p = std::make_unique<int>(42);  // Scope sonunda otomatik delete
auto shared = std::make_shared<int>(42);  // Paylaşılan sahiplik

3. Bounds Check Yap

// ❌ Kontrolsüz erişim
vec[i] = 10;  // i >= vec.size() ise UB

// ✅ Kontrollü erişim
if (i < vec.size()) {
    vec[i] = 10;
}
// veya
vec.at(i) = 10;  // Otomatik sınır kontrolü + exception

4. Const Correctness

// ❌ Her şey mutable — accidental modification riski
void isle(std::vector<int>& veri, int carpan) {
    carpan = 0;  // Oops, yanlışlıkla değiştirdim
    for (auto& v : veri) v *= carpan;  // Hep 0 yapıyor
}

// ✅ Const ile koruma
void isle(std::vector<int>& veri, const int carpan) {
    // carpan = 0;  // Derleme hatası!
    for (auto& v : veri) v *= carpan;
}

// ✅ Const referans — gereksiz kopyayı önle ve değişikliğe izin verme
void yazdir(const std::vector<int>& veri) {
    for (const auto& v : veri) {
        std::cout << v << " ";
    }
}

5. Defensive Programming ile Tamamla

#include <cassert>
#include <stdexcept>

// Precondition kontrolü
double karekokAl(double x) {
    if (x < 0) {
        throw std::domain_error("Negatif sayinin karekoku alinamaz");
    }
    return std::sqrt(x);
}

// Debug build'de assert
void diziIsle(const int* dizi, size_t boyut) {
    assert(dizi != nullptr && "dizi null olamaz");
    assert(boyut > 0 && "boyut pozitif olmali");
    
    for (size_t i = 0; i < boyut; ++i) {
        // ... güvenli işlem
    }
}

Gerçek Dünya Örneği: Güvenli Buffer Sınıfı

Tüm safe coding prensiplerini bir araya getiren bir örnek:

#include <iostream>
#include <vector>
#include <stdexcept>
#include <algorithm>
#include <cassert>
#include <cstring>

// Güvenli, UB-free buffer sınıfı
template <typename T>
class GuvenliBuffer {
public:
    // Her zaman başlat — default değerlerle
    explicit GuvenliBuffer(size_t boyut)
        : veri_(boyut, T{})  // Tüm elemanlar default-initialized
    {
        if (boyut == 0) {
            throw std::invalid_argument("Buffer boyutu 0 olamaz");
        }
    }
    
    // Bounds-checked erişim
    T& operator[](size_t index) {
        boundsCheck(index);
        return veri_[index];
    }
    
    const T& operator[](size_t index) const {
        boundsCheck(index);
        return veri_[index];
    }
    
    // Güvenli kopyalama — overflow imkansız
    void kopyala(const T* kaynak, size_t adet) {
        if (kaynak == nullptr) {
            throw std::invalid_argument("Kaynak null olamaz");
        }
        if (adet > veri_.size()) {
            throw std::out_of_range("Kopyalanacak adet buffer boyutunu asiyor");
        }
        std::copy(kaynak, kaynak + adet, veri_.begin());
    }
    
    // İteratör desteği — range-based for loop
    auto begin() { return veri_.begin(); }
    auto end() { return veri_.end(); }
    auto begin() const { return veri_.cbegin(); }
    auto end() const { return veri_.cend(); }
    
    size_t boyut() const { return veri_.size(); }
    bool bos() const { return veri_.empty(); }
    
    // Güvenli yeniden boyutlandırma
    void yenidenBoyutlandir(size_t yeniBoyut) {
        if (yeniBoyut == 0) {
            throw std::invalid_argument("Yeni boyut 0 olamaz");
        }
        veri_.resize(yeniBoyut, T{});  // Yeni elemanlar default-initialized
    }

private:
    void boundsCheck(size_t index) const {
        if (index >= veri_.size()) {
            throw std::out_of_range(
                "Index sinir disi: " + std::to_string(index) + 
                " (boyut: " + std::to_string(veri_.size()) + ")"
            );
        }
    }
    
    std::vector<T> veri_;  // RAII — otomatik bellek yönetimi
};

int main() {
    try {
        GuvenliBuffer<int> buf(10);
        
        // Güvenli yazma
        for (size_t i = 0; i < buf.boyut(); ++i) {
            buf[i] = static_cast<int>(i * 10);
        }
        
        // Range-based for — güvenli okuma
        for (const auto& val : buf) {
            std::cout << val << " ";
        }
        std::cout << std::endl;
        
        // Sınır dışı erişim — exception, UB değil
        buf[100] = 42;  // throws out_of_range
        
    } catch (const std::exception& e) {
        std::cout << "Hata yakalandi: " << e.what() << std::endl;
    }
    
    return 0;
}

Bu sınıfta:

  • Buffer overflow imkansız: Her erişim bounds-checked

  • Dangling pointer imkansız: std::vector RAII ile bellek yönetiyor

  • Use-after-free imkansız: Smart pointer / RAII — manuel delete yok

  • Başlatılmamış bellek imkansız: Tüm elemanlar T{} ile başlatılıyor

  • Null pointer dereference kontrollü: kopyala() fonksiyonu null kontrolü yapıyor


C++ Core Guidelines Referansı

Bjarne Stroustrup ve Herb Sutter tarafından yönetilen [C++ Core Guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines) projenin kapsamlı bir güvenli kodlama rehberidir. Bazı kritik kurallar:

KuralAçıklama
R.1Kaynak sahipliğini smart pointer'larla yönet
R.3Raw pointer sahiplik ifade etmez
I.12Null olamayacak pointer'ı referans olarak al
ES.20Her nesneyi her zaman başlat
ES.42Pointer'ları basit ve düz tut
C.149Çıplak new/delete kullanma
CP.2Data race'den kaçın
E.6Garanti veremiyorsan RAII kullan

clang-tidy aracı bu kuralları otomatik kontrol edebilir:

clang-tidy program.cpp --checks='-*,cppcoreguidelines-*'

💡 Pratik tavsiye: Her yeni C++ projene sanitizer'ları (ASan + UBSan) baştan ekle. Bug'ları production'da değil, geliştirme sırasında yakala. CI/CD pipeline'ına sanitizer build eklemek, güvenlik için en yüksek getirili yatırımdır.


Özet

  • Undefined Behavior, C++ standardının "ne olacağını tanımlamıyorum" dediği durumlardır. Derleyici UB olmayacağını varsayarak optimize eder — bu da beklenmedik sonuçlara yol açar.

  • En yaygın UB'ler: buffer overflow, dangling pointer, use-after-free, double free, signed integer overflow, null dereference, data race.

  • Sanitizer'lar (ASan, UBSan, TSan, MSan) derleyici desteğiyle UB'yi çalışma zamanında tespit eder. Geliştirme ve CI'da mutlaka kullan.

  • Valgrind yeniden derleme gerektirmeden bellek hatalarını bulur. Daha yavaş ama kapsamlıdır.

  • Safe coding: Her zaman başlat, smart pointer kullan, sınır kontrolü yap, const correctness uygula, defensive programming ile koru.

  • C++ Core Guidelines endüstri standardı güvenli kodlama rehberidir. clang-tidy ile otomatik kontrol et.

  • Modern C++'ta raw new/delete neredeyse hiç kullanılmaz. RAII, smart pointer ve standart kütüphane container'ları UB'nin çoğunu yapısal olarak ortadan kaldırır.