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:
| Kategori | Tanım | Örnek |
|---|---|---|
| Defined | Davranış tam olarak belirli | int x = 5 + 3; → her zaman 8 |
| Implementation-defined | Derleyiciye bağlı ama tutarlı | sizeof(int) → 4 veya 8, ama tutarlı |
| Undefined | Hiçbir garanti yok | Signed 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
./programASan 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:4Bu 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
./programprogram.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
./programWARNING: 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:7MemorySanitizer (MSan)
Başlatılmamış bellek okumalarını yakalar. Sadece Clang'da mevcuttur.
clang++ -fsanitize=memory -g program.cpp -o program
./programSanitizer'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/programValgrind: 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:
| Özellik | ASan | Valgrind |
|---|---|---|
| Hız | 2x yavaşlatma | 10-50x yavaşlatma |
| Yeniden derleme | Gerekli | Gereksiz |
| Kapsam | Buffer overflow, UAF, leak | Leak, uninit read, genel bellek |
| Platform | GCC, Clang | Linux, 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 02. 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 sahiplik3. 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ü + exception4. 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::vectorRAII ile bellek yönetiyorUse-after-free imkansız: Smart pointer / RAII — manuel delete yok
Başlatılmamış bellek imkansız: Tüm elemanlar
T{}ile başlatılıyorNull 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:
| Kural | Açıklama |
|---|---|
| R.1 | Kaynak sahipliğini smart pointer'larla yönet |
| R.3 | Raw pointer sahiplik ifade etmez |
| I.12 | Null olamayacak pointer'ı referans olarak al |
| ES.20 | Her nesneyi her zaman başlat |
| ES.42 | Pointer'ları basit ve düz tut |
| C.149 | Çıplak new/delete kullanma |
| CP.2 | Data race'den kaçın |
| E.6 | Garanti 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-tidyile otomatik kontrol et.Modern C++'ta raw
new/deleteneredeyse hiç kullanılmaz. RAII, smart pointer ve standart kütüphane container'ları UB'nin çoğunu yapısal olarak ortadan kaldırır.
AI Asistan
Sorularını yanıtlamaya hazır