← Kursa Dön
📄 Text · 15 min

ABI Uyumluluğu

Bir şehirdeki elektrik prizi standardını düşün. Türkiye'de F tipi priz kullanılır — iki yuvarlak delikli. Almanya'dan aldığın bir cihazın fişi bu prize uyar çünkü aynı standardı kullanırlar. Ama İngiltere'den aldığın cihaz? Üç dikdörtgen pinli G tipi fiş. Cihazın kendisi mükemmel çalışıyor, Türk prizi de mükemmel çalışıyor — ama birbirine fiziksel olarak uymuyorlar. Adaptör lazım. İşte ABI uyumluluğu tam olarak bu: iki binary'nin (derlenmiş kodun) birbirleriyle fiziksel düzeyde konuşabilmesi. Kaynak kodlar uyumlu olabilir ama derlenmiş halleri uyumsuz olabilir.

ABI konusu, özellikle kütüphane geliştiricileri ve uzun ömürlü projeler için kritiktir. Bir .so veya .dll dosyası yayınlıyorsan, kullanıcıların her güncelleme sonrası yeniden derlemesini gerektirmeden güncelleme yapabilmek istiyorsun. Bu da ABI'yi anlamayı zorunlu kılıyor.


ABI Nedir?

ABI (Application Binary Interface), derlenmiş kodun (binary) düzeyindeki arayüzdür. Şunları tanımlar:

  • Fonksiyon çağrı konvansiyonu: Argümanlar registerlara mı yoksa stack'e mi konulur? Dönüş değeri nereye yazılır?

  • Veri yapısı layout: Struct üyeleri bellekte nasıl sıralanır? Padding nasıl eklenir?

  • Name mangling: C++ fonksiyon isimleri binary'de nasıl temsil edilir?

  • Vtable düzeni: Sanal fonksiyon tablosu nasıl organize edilir?

  • Exception handling mekanizması: Exception bilgisi nasıl taşınır?

  • Temel tiplerin boyutu ve hizalaması: int, long, pointer boyutları

ABI vs API: Kritik Fark

APIABI
SeviyeKaynak kodBinary (derlenmiş kod)
Ne tanımlar?Fonksiyon imzaları, tip isimleriBellek düzeni, çağrı konvansiyonu
Uyumluluk kontrolüDerleme zamanında (derleyici)Yükleme/çalışma zamanında (linker/loader)
Kırıldığında ne olur?Derleme hatasıCrash, veri bozulması, sessiz hata
Örnekvoid foo(int x)void foo(int x, int y)Struct'a yeni üye eklemek

API kırıldığında derleyici seni uyarır. ABI kırıldığında hiçbir uyarı almadan crash edersin. Bu yüzden ABI daha tehlikelidir.

// API değişikliği — derleyici yakalar
// Eski: void process(int x);
// Yeni: void process(int x, int y);
// Kullanıcı: process(5); → DERLEME HATASI (güvenli!)

// ABI değişikliği — derleyici YAKALAMAZ
// Kütüphane header:
struct Config {
    int width;
    int height;
    // Yeni versiyon: bool fullscreen; EKLENDI
};
// Kullanıcı eski header ile derledi → sizeof(Config) farklı → CRASH!

Name Mangling

C++ fonksiyon overloading destekler: aynı isimde farklı parametre listeli fonksiyonlar olabilir. Ama linker düzeyinde her sembol benzersiz olmalı. Bu sorunu çözen mekanizma name mangling'dir.

// Kaynak kod
namespace math {
    int add(int a, int b);
    double add(double a, double b);
    int add(int a, int b, int c);
}

class Calculator {
public:
    int compute(int x);
    int compute(int x, int y);
};

Derleyici bu fonksiyonları benzersiz isimlere dönüştürür. GCC/Clang'da (Itanium ABI):

FonksiyonMangled İsim
math::add(int, int)_ZN4math3addEii
math::add(double, double)_ZN4math3addEdd
math::add(int, int, int)_ZN4math3addEiii
Calculator::compute(int)_ZN10Calculator7computeEi
Calculator::compute(int, int)_ZN10Calculator7computeEii

MSVC tamamen farklı bir mangling şeması kullanır. Bu, GCC ile derlenen bir kütüphanenin MSVC ile derlenen bir programla linklenemeyeceği anlamına gelir — aynı platform, aynı işlemci olsa bile.

Mangled İsimleri Görmek

# Derlenmiş binary'deki sembolleri göster
nm -C my_library.so        # -C: demangle et (okunabilir)
nm my_library.so            # Mangled haliyle göster

# Tek bir ismi demangle et
echo "_ZN4math3addEii" | c++filt
# Çıktı: math::add(int, int)

# Bir ismi mangle et (derleyiciye sor)
echo 'int math::add(int, int)' | g++ -x c++ - -S -o - 2>/dev/null | grep _ZN

ABI Kırılma Nedenleri

1. Struct/Class Layout Değişikliği

// Versiyon 1.0
struct Packet {
    uint32_t id;       // offset 0
    uint16_t type;     // offset 4
    uint16_t flags;    // offset 6
};  // sizeof = 8

// Versiyon 1.1 — ABI KIRILDI!
struct Packet {
    uint32_t id;       // offset 0
    uint32_t sequence; // YENI — offset 4  ← type ve flags kaydı!
    uint16_t type;     // offset 8 (eskiden 4 idi!)
    uint16_t flags;    // offset 10 (eskiden 6 idi!)
};  // sizeof = 12

// Eski kodla derlenen program: packet.type'a offset 4'ten erişir
// Ama yeni struct'ta offset 4'te "sequence" var!
// Sonuç: yanlış veri okunur, crash veya sessiz veri bozulması

2. Vtable Değişikliği

// Versiyon 1.0
class Shape {
public:
    virtual double area() = 0;        // vtable[0]
    virtual double perimeter() = 0;   // vtable[1]
    virtual ~Shape() = default;       // vtable[2]
};

// Versiyon 1.1 — ABI KIRILDI!
class Shape {
public:
    virtual std::string name() = 0;   // vtable[0] — YENI!
    virtual double area() = 0;        // vtable[1] (eskiden [0])
    virtual double perimeter() = 0;   // vtable[2] (eskiden [1])
    virtual ~Shape() = default;       // vtable[3] (eskiden [2])
};

// Eski kodla derlenen program: vtable[0]'ı çağırır → area() bekliyor
// Ama yeni vtable'da [0] = name() → yanlış fonksiyon çağrılır!

Mevcut sanal fonksiyonların önüne yeni sanal fonksiyon eklemek vtable'ı kaydırır. Sona eklemek ise yalnızca doğrudan bu sınıftan miras alan kodlar için güvenli olabilir — ama genel olarak risklidir.

3. STL Container Layout

// Farklı derleyici versiyonlarında std::string layout değişebilir
// GCC'nin std::string'i copy-on-write'dan small-string-optimization'a geçti
// (GCC 5 = ABI break, libstdc++.so.6'nın dual ABI desteği bu yüzden var)

// Kütüphane arayüzünde STL tipi döndürmek risklidir:
std::vector<std::string> get_names();  // ⚠️ ABI'ye bağlı

// Güvenli alternatif:
size_t get_names(const char** buffer, size_t max_count);  // C ABI — stabil

Tam ABI Kırılma Listesi

DeğişiklikABI Kırar mı?
Yeni fonksiyon ekle❌ Hayır
Fonksiyon kaldır✅ Evet
Struct'a üye ekle✅ Evet
Sanal fonksiyon ekle/kaldır/sırala✅ Evet
Non-virtual'ı virtual yap✅ Evet
Miras hiyerarşisi değiştir✅ Evet
Enum değeri ekle (sonuna)❌ Genellikle hayır
Fonksiyon parametresi ekle✅ Evet (name mangling)
Template instansiasyon değiştir✅ Evet
Derleyici/STL versiyonu değiştir⚠️ Olabilir

Pimpl Idiom: ABI Stabilite Kalkanı

Pimpl (Pointer to Implementation), sınıfın implementasyon detaylarını bir opaque pointer arkasına saklar. Header'da sadece pointer görünür, implementasyon .cpp dosyasında kalır. Bu, implementasyon değiştiğinde header'ın ve dolayısıyla ABI'nin değişmemesini sağlar.

// ---- database.h (PUBLIC HEADER — kullanıcılar bunu include eder) ----
#pragma once
#include <memory>
#include <string>
#include <cstdint>

class Database {
public:
    Database(const std::string& connection_string);
    ~Database();
    
    // Move semantics
    Database(Database&&) noexcept;
    Database& operator=(Database&&) noexcept;
    
    // Copy yasak (unique_ptr)
    Database(const Database&) = delete;
    Database& operator=(const Database&) = delete;
    
    // Public API
    bool connect();
    void disconnect();
    bool execute(const std::string& query);
    int64_t last_insert_id() const;
    
private:
    // Implementasyonu TAMAMEN gizle
    struct Impl;                     // Forward declaration
    std::unique_ptr<Impl> pimpl_;   // Opaque pointer
};

// sizeof(Database) = sizeof(unique_ptr<Impl>) = sizeof(void*) = 8 byte
// Bu boyut ASLA değişmez — ne eklersen ekle Impl'e!
// ---- database.cpp (PRIVATE — kütüphane içinde kalır) ----
#include "database.h"
#include <vector>
#include <unordered_map>
#include <mutex>

// İmplementasyon — istediğin kadar değiştir, ABI kırılmaz
struct Database::Impl {
    std::string connection_string;
    bool connected = false;
    int64_t last_id = 0;
    
    // Bu alanları ekle, kaldır, değiştir — ABI etkilenmez!
    std::vector<std::string> query_history;
    std::unordered_map<std::string, std::string> cache;
    std::mutex mutex;
    int retry_count = 3;           // Yeni eklendi — ABI kırılmadı!
    double timeout_seconds = 30.0; // Yeni eklendi — ABI kırılmadı!
    
    bool do_connect() {
        // Gerçek bağlantı mantığı...
        connected = true;
        return true;
    }
    
    bool do_execute(const std::string& query) {
        std::lock_guard<std::mutex> lock(mutex);
        query_history.push_back(query);
        // Gerçek sorgu çalıştırma...
        last_id = 42;
        return true;
    }
};

Database::Database(const std::string& connection_string)
    : pimpl_(std::make_unique<Impl>())
{
    pimpl_->connection_string = connection_string;
}

Database::~Database() = default;
Database::Database(Database&&) noexcept = default;
Database& Database::operator=(Database&&) noexcept = default;

bool Database::connect() { return pimpl_->do_connect(); }
void Database::disconnect() { pimpl_->connected = false; }
bool Database::execute(const std::string& query) { return pimpl_->do_execute(query); }
int64_t Database::last_insert_id() const { return pimpl_->last_id; }

Pimpl'in gücü: Impl struct'ına istediğin kadar alan ekle, kaldır, değiştir — header değişmediği sürece ABI kırılmaz. Kullanıcıların yeniden derlemesine gerek kalmaz.

Pimpl'in Maliyeti

  • Heap allocation: Her nesne için make_unique çağrısı (ama Impl genellikle büyük, maliyet göreceli olarak düşük)

  • İndirection: Her metot çağrısı bir pointer dereference ekler (genellikle önemsiz)

  • Boilerplate: Forwarding fonksiyonları yazmak gerekir

Performans kritik iç sınıflarda Pimpl gereksiz. Ama public API sınıflarında (kütüphane arayüzü) ABI stabilitesi genellikle bu maliyete değer.


extern "C" ve C-Uyumlu Interface

C++ name mangling yüzünden, farklı derleyicilerle derlenmiş C++ kütüphaneleri birbirleriyle iletişim kuramayabilir. Ama C ABI evrenseldir — her platform aynı C çağrı konvansiyonunu kullanır. Bu yüzden kütüphane arayüzleri sıklıkla C ABI ile expose edilir.

// ---- mylib.h (C ve C++ tarafından kullanılabilir) ----
#pragma once

#ifdef __cplusplus
extern "C" {
#endif

// Opaque handle (C'de class yok, pointer kullanırız)
typedef struct MyEngine_t* MyEngine;

// Yaşam döngüsü
MyEngine myengine_create(const char* config_path);
void     myengine_destroy(MyEngine engine);

// İşlemler
int         myengine_process(MyEngine engine, const char* input);
const char* myengine_get_result(MyEngine engine);
const char* myengine_get_error(MyEngine engine);

// Versiyon bilgisi
int         myengine_version_major(void);
int         myengine_version_minor(void);

#ifdef __cplusplus
}
#endif
// ---- mylib.cpp (C++ implementasyon) ----
#include "mylib.h"
#include <string>
#include <memory>

// İç implementasyon — tamamen C++
class EngineImpl {
public:
    explicit EngineImpl(const std::string& config) : config_(config) {}
    
    int process(const std::string& input) {
        try {
            result_ = "Processed: " + input;
            return 0;  // Success
        } catch (const std::exception& e) {
            error_ = e.what();
            return -1;  // Error
        }
    }
    
    const std::string& result() const { return result_; }
    const std::string& error() const { return error_; }
    
private:
    std::string config_;
    std::string result_;
    std::string error_;
};

// C ABI wrapper fonksiyonları
extern "C" {

MyEngine myengine_create(const char* config_path) {
    try {
        return reinterpret_cast<MyEngine>(new EngineImpl(config_path));
    } catch (...) {
        return nullptr;
    }
}

void myengine_destroy(MyEngine engine) {
    delete reinterpret_cast<EngineImpl*>(engine);
}

int myengine_process(MyEngine engine, const char* input) {
    if (!engine || !input) return -1;
    return reinterpret_cast<EngineImpl*>(engine)->process(input);
}

const char* myengine_get_result(MyEngine engine) {
    if (!engine) return "";
    return reinterpret_cast<EngineImpl*>(engine)->result().c_str();
}

const char* myengine_get_error(MyEngine engine) {
    if (!engine) return "";
    return reinterpret_cast<EngineImpl*>(engine)->error().c_str();
}

int myengine_version_major(void) { return 2; }
int myengine_version_minor(void) { return 1; }

}

Bu pattern'da C++ implementasyonu tamamen gizli. Dışarıya sadece C fonksiyonları açılıyor. Bu sayede:

  • Her C++ derleyicisi kütüphaneyi kullanabilir (GCC, Clang, MSVC)

  • Python, Rust, Go, Java gibi diller FFI ile doğrudan çağırabilir

  • ABI stabilitesi maksimum — C ABI neredeyse hiç değişmez

⚠️ Dikkat: extern "C" fonksiyonlar exception fırlatamazlar (C'de exception mekanizması yok). C++ exception'ı C koduna ulaşırsa undefined behavior olur. Her extern "C" fonksiyonda try-catch bloğu ile exception'ları hata koduna dönüştürmek şart.


Inline Namespace ile Versioning

C++11'de gelen inline namespace, ABI versiyonlamayı dil düzeyinde destekler. Kullanıcılar namespace'i görmez ama linker farklı versiyonları birbirinden ayırabilir.

// ---- Versiyon 1: mylib v1 ----
namespace mylib {
    inline namespace v1 {
        struct Config {
            int width;
            int height;
        };
        
        void init(const Config& cfg);
    }
}

// Kullanıcı kodu:
// mylib::Config cfg{800, 600};  // Aslında mylib::v1::Config
// mylib::init(cfg);             // Aslında mylib::v1::init
// Mangled: _ZN5mylib2v14initERKNS0_6ConfigE

// ---- Versiyon 2: mylib v2 ----
namespace mylib {
    namespace v1 {  // Artık inline DEĞİL
        struct Config {
            int width;
            int height;
        };
        void init(const Config& cfg);
    }
    
    inline namespace v2 {  // Yeni versiyon inline
        struct Config {
            int width;
            int height;
            bool fullscreen;   // Yeni alan — ABI farklı
            int refresh_rate;  // Yeni alan
        };
        void init(const Config& cfg);
    }
}

// Yeni kullanıcı kodu:
// mylib::Config cfg{800, 600, true, 144};  // mylib::v2::Config
// mylib::init(cfg);                         // mylib::v2::init
// Mangled: _ZN5mylib2v24initERKNS0_6ConfigE  — v1'den FARKLI!

// Eski kodu açıkça v1 ile kullanmak isteyen:
// mylib::v1::Config cfg{800, 600};
// mylib::v1::init(cfg);

Linker düzeyinde v1::init ve v2::init farklı sembollerdir. Bu, aynı binary'de iki versiyonun birlikte var olmasını sağlar. libstdc++ bunu string implementasyonu değiştiğinde (GCC 5 ABI break) kullanmıştır.

Gerçek Dünya: libstdc++ Dual ABI

// GCC 5+ libstdc++ dual ABI
namespace std {
    // Eski ABI (COW string)
    namespace __cxx03 {
        class basic_string { /* ... */ };
    }
    
    // Yeni ABI (SSO string) — varsayılan
    inline namespace __cxx11 {
        class basic_string { /* ... */ };
    }
}

// _GLIBCXX_USE_CXX11_ABI=0 → eski ABI
// _GLIBCXX_USE_CXX11_ABI=1 → yeni ABI (varsayılan)

Bu sayede GCC 5 öncesi derlenmiş kütüphaneler hâlâ çalışır — __cxx03 namespace'indeki string'i kullanmaya devam ederler.


Kütüphane Versiyonlama

SONAME (Linux Shared Library Versioning)

Linux'ta shared library'ler SONAME (Shared Object Name) ile versiyonlanır:

# Kütüphane dosya isimlendirme konvansiyonu
libmylib.so              # Sembolik link → development
libmylib.so.2            # Sembolik link → SONAME (major versiyon)
libmylib.so.2.1          # Sembolik link → tam versiyon
libmylib.so.2.1.3        # Gerçek dosya

# SONAME ayarlama (CMake)
# CMakeLists.txt
set_target_properties(mylib PROPERTIES
    VERSION   2.1.3     # Tam versiyon
    SOVERSION 2         # Major versiyon (ABI versiyonu)
)
# CMake ile tam versiyonlama örneği
cmake_minimum_required(VERSION 3.16)
project(mylib VERSION 2.1.3 LANGUAGES CXX)

add_library(mylib SHARED
    src/mylib.cpp
)

target_include_directories(mylib PUBLIC
    $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
)

set_target_properties(mylib PROPERTIES
    VERSION ${PROJECT_VERSION}
    SOVERSION ${PROJECT_VERSION_MAJOR}
    CXX_VISIBILITY_PRESET hidden        # Sadece export edilen semboller görünür
    VISIBILITY_INLINES_HIDDEN ON
)

# Export makrosu
include(GenerateExportHeader)
generate_export_header(mylib
    EXPORT_FILE_NAME ${CMAKE_BINARY_DIR}/include/mylib_export.h
)

Semantic Versioning (SemVer)

MAJOR.MINOR.PATCH

MAJOR: ABI kırılan değişiklik (struct layout, fonksiyon kaldırma)
MINOR: Geriye uyumlu yeni özellik (yeni fonksiyon ekleme)
PATCH: Bug fix (davranış değişikliği yok)
DeğişiklikVersiyon Artışı
Bug fix2.1.3 → 2.1.4
Yeni fonksiyon ekleme2.1.3 → 2.2.0
Struct layout değişikliği2.1.3 → 3.0.0
Sanal fonksiyon ekleme2.1.3 → 3.0.0
Fonksiyon kaldırma2.1.3 → 3.0.0

Symbol Visibility

Tüm sembolleri export etmek yerine, sadece public API'yi export et. Bu hem ABI yüzeyini küçültür hem de link performansını artırır.

// Export makrosu — platform bağımsız
#if defined(_WIN32)
    #ifdef MYLIB_BUILDING
        #define MYLIB_API __declspec(dllexport)
    #else
        #define MYLIB_API __declspec(dllimport)
    #endif
#elif defined(__GNUC__) || defined(__clang__)
    #define MYLIB_API __attribute__((visibility("default")))
#else
    #define MYLIB_API
#endif

// Sadece export edilen fonksiyonlar dışarıdan erişilebilir
class MYLIB_API PublicClass {
public:
    void public_method();
private:
    void internal_method();  // Export edilir ama private (ABI'nin parçası)
};

// Export EDİLMEYEN — kütüphane dışından erişilemez
class InternalHelper {
    // Bu sınıf dilediğin gibi değiştirilebilir — ABI etkilenmez
};

💡 İpucu: CMake'in generate_export_header komutu, platform bağımsız export makrolarını otomatik oluşturur. Elle #ifdef _WIN32 yazmak yerine bunu kullan.


ABI Checker Araçları

abi-compliance-checker

İki kütüphane versiyonu arasındaki ABI farklarını otomatik tespit eder.

# Kurulum (Ubuntu/Debian)
sudo apt install abi-compliance-checker abi-dumper

# Adım 1: Eski versiyonun ABI dump'ını al
abi-dumper libmylib.so.1.0.0 -o v1.dump -lver 1.0

# Adım 2: Yeni versiyonun ABI dump'ını al
abi-dumper libmylib.so.2.0.0 -o v2.dump -lver 2.0

# Adım 3: Karşılaştır
abi-compliance-checker -l mylib -old v1.dump -new v2.dump

# Çıktı: HTML rapor — kırılan, eklenen, değişen semboller

Rapor şunları gösterir:

  • Kaldırılan semboller (fonksiyon/sınıf)

  • Değişen struct/class layout (boyut, üye offset)

  • Değişen fonksiyon imzaları

  • Vtable değişiklikleri

  • Uyumluluk yüzdesi (örn: %87 binary uyumluluk)

Diğer Araçlar

# libabigail — Red Hat'in ABI analiz aracı
abidiff libmylib.so.old libmylib.so.new

# nm — sembol listesi
nm -DC libmylib.so | head -20  # -D: dynamic, -C: demangle

# readelf — ELF binary analizi
readelf -d libmylib.so | grep SONAME  # SONAME kontrol
readelf -s libmylib.so               # Sembol tablosu

# objdump — detaylı analiz
objdump -T libmylib.so  # Dynamic sembol tablosu

Gerçek Dünya Örneği: ABI-Stabil Kütüphane

Tüm kavramları birleştiren bir örnek: Pimpl, extern "C", symbol visibility, versioning.

// ---- include/imagelib.h (Public API) ----
#pragma once
#include <cstdint>
#include <cstddef>

// Platform-bağımsız export makrosu
#if defined(IMAGELIB_SHARED)
    #if defined(_WIN32)
        #ifdef IMAGELIB_BUILDING
            #define IMAGELIB_API __declspec(dllexport)
        #else
            #define IMAGELIB_API __declspec(dllimport)
        #endif
    #else
        #define IMAGELIB_API __attribute__((visibility("default")))
    #endif
#else
    #define IMAGELIB_API
#endif

// C++ API (Pimpl ile ABI-stabil)
#ifdef __cplusplus
#include <memory>
#include <string>

namespace imagelib {
    inline namespace v2 {  // ABI versiyonu
        
        class IMAGELIB_API Image {
        public:
            Image();
            ~Image();
            Image(Image&&) noexcept;
            Image& operator=(Image&&) noexcept;
            
            bool load(const std::string& path);
            bool save(const std::string& path) const;
            
            int32_t width() const;
            int32_t height() const;
            int32_t channels() const;
            
            const uint8_t* data() const;
            
            bool resize(int32_t new_width, int32_t new_height);
            bool to_grayscale();
            
        private:
            struct Impl;
            std::unique_ptr<Impl> pimpl_;
        };
        
    } // namespace v2
} // namespace imagelib

#endif // __cplusplus

// C API (maksimum uyumluluk)
#ifdef __cplusplus
extern "C" {
#endif

typedef struct ImageHandle_t* ImageHandle;

IMAGELIB_API ImageHandle imagelib_create(void);
IMAGELIB_API void        imagelib_destroy(ImageHandle img);
IMAGELIB_API int         imagelib_load(ImageHandle img, const char* path);
IMAGELIB_API int         imagelib_save(ImageHandle img, const char* path);
IMAGELIB_API int32_t     imagelib_width(ImageHandle img);
IMAGELIB_API int32_t     imagelib_height(ImageHandle img);
IMAGELIB_API int         imagelib_resize(ImageHandle img, int32_t w, int32_t h);
IMAGELIB_API int         imagelib_to_grayscale(ImageHandle img);

// Versiyon bilgisi
IMAGELIB_API int imagelib_version_major(void);
IMAGELIB_API int imagelib_version_minor(void);
IMAGELIB_API int imagelib_version_patch(void);
IMAGELIB_API const char* imagelib_version_string(void);

#ifdef __cplusplus
}
#endif

Bu header, hem C++ hem C kullanıcılarına hitap eder. C++ API Pimpl ile korunur, C API opaque handle ile korunur. inline namespace v2 ile ABI versiyonu linker düzeyinde takip edilir. Symbol visibility ile sadece IMAGELIB_API işaretli semboller export edilir.

ABI uyumluluğu garantisi vermek ciddi bir taahhüttür. Her yeni versiyon çıkışında ABI checker çalıştır, CI pipeline'ına entegre et ve major versiyon artışlarını açıkça belgele. Bir kez ABI promise verdiğinde, geri dönmek zordur.


Özet

  • ABI (Application Binary Interface), derlenmiş kodun bellek düzeni, çağrı konvansiyonu ve sembol isimlerini tanımlar — API uyumlu ama ABI uyumsuz kod sessizce crash eder.

  • Name mangling, C++ overloaded fonksiyonları linker düzeyinde benzersiz kılar — ama derleyiciler arası uyumsuzluğun temel nedenidir.

  • Struct üyesi ekleme, sanal fonksiyon sırası değiştirme, miras değişikliği ABI kıran en yaygın değişikliklerdir — her biri sessiz data corruption'a yol açabilir.

  • Pimpl idiom, implementasyonu opaque pointer arkasına saklayarak header'ı (ve dolayısıyla ABI'yi) stabilize eder — public kütüphane sınıfları için standart yaklaşımdır.

  • extern "C", C++ name mangling'i devre dışı bırakarak evrensel C ABI ile dışa açılım sağlar — cross-language FFI için zorunlu, exception'ları try-catch ile yakalamak şart.

  • Inline namespace ve SONAME/SemVer ile kütüphane versiyonlama, eski ve yeni kodun güvenle birlikte çalışmasını sağlar — abi-compliance-checker gibi araçlarla CI'da otomatik kontrol et.