C# Unsafe Kod ve İşaretçiler: Belleğe Doğrudan Dokunmak

c-unsafe-kod-73

C# çoğunlukla güvenli ve yönetilen bir dünyada çalışır: Belleği çöp toplayıcı düzenler, dizilerin sınırları denetlenir ve geliştirici adres hesaplarıyla uğraşmaz. Ancak görüntü işleme, oyun motorları, yerel kütüphane entegrasyonu veya çok düşük gecikmeli sistemler söz konusu olduğunda bu konforun küçük bir performans maliyeti olabilir. unsafe kod, C# geliştiricisine C++ benzeri işaretçilerle belleğe doğrudan erişim imkânı verir. Güçlüdür; fakat emniyet kemerini çıkarmaya benzediği için dikkatli kullanılmalıdır.

``

Yönetilen bellekten işaretçilere

Normal C# referansı bir nesneyi gösterir, ancak nesnenin fiziksel adresini geliştiriciye açmaz. İşaretçi ise bellekteki adresi doğrudan tutar. int* ifadesi, bir int değerinin adresini taşıyan işaretçi anlamına gelir.

Bir işaretçinin kaba bellek modeli şöyle ifade edilebilir:

\[\text{hedef adres} = \text{başlangıç adresi} + i \times \text{veri tipi boyutu}\]

Örneğin int 4 baytsa p + 3, başlangıçtan yaklaşık $3 \times 4 = 12$ bayt ilerideki elemana ulaşır. İşaretçi aritmetiği bayt yerine hedef türün boyutunu hesaba kattığından kullanışlıdır.

Özellik Güvenli C# Unsafe C#
Bellek erişimi Referanslar üzerinden Doğrudan adres üzerinden
Sınır kontrolü Genellikle var İşaretçide yok
Çöp toplayıcı uyumu Otomatik Sabitleme gerekebilir
Hata riski Daha düşük Bellek bozulması mümkün
Tipik kullanım İş uygulamaları Yerel API, medya, oyun, optimizasyon

Unsafe kodu etkinleştirme

Önce proje dosyasına aşağıdaki ayar eklenir:

<PropertyGroup>
  <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

Visual Studio kullanılıyorsa aynı seçenek proje özelliklerindeki Build > Allow code that uses the unsafe keyword alanından etkinleştirilebilir. Ardından yalnızca gerekli metot veya blok unsafe olarak işaretlenmelidir; tüm sınıfı tehlike bölgesine çevirmek şart değildir.

unsafe static void DegeriDegistir()
{
    int sayi = 10;
    int* adres = &sayi;

    *adres = 42;
    Console.WriteLine(sayi);
}

Burada &sayi, değişkenin adresini alır. *adres ise o adresteki değere erişir; buna dereference işlemi denir. Sonuç 42 olur. Yanlış veya artık geçerli olmayan bir adres kullanılırsa çalışma zamanı her zaman sizi kurtaramaz.

GC hareketine karşı fixed

Çöp toplayıcı, belleği sıkıştırırken yönetilen nesnelerin yerini değiştirebilir. Bir dizi işaretçiyle işlenirken taşınırsa işaretçi eski adresi göstermeye devam eder. fixed, nesneyi blok süresince yerine sabitler:

unsafe static int Topla(int[] sayilar)
{
    int toplam = 0;

    fixed (int* baslangic = sayilar)
    {
        for (int i = 0; i < sayilar.Length; i++)
            toplam += *(baslangic + i);
    }

    return toplam;
}

Bu kod dizinin başlangıç adresini alır ve işaretçi aritmetiğiyle elemanları gezer. fixed blokları kısa tutulmalıdır; uzun süre sabitlenen nesneler çöp toplayıcının belleği verimli düzenlemesini zorlaştırabilir.

stackalloc ve modern alternatifler

Küçük, geçici tamponlar yığında oluşturulabilir:

unsafe static int KarelerToplami(int adet)
{
    int* tampon = stackalloc int[adet];
    int toplam = 0;

    for (int i = 0; i < adet; i++)
    {
        tampon[i] = i * i;
        toplam += tampon[i];
    }

    return toplam;
}

stackalloc, çöp toplayıcıya yük bindirmez; ancak yığın kapasitesi sınırlıdır. Büyük veya kullanıcı tarafından kontrol edilen boyutlar yığın taşmasına yol açabilir. Ayrıca pek çok senaryoda Span<T> aynı performans hedefini daha güvenli sınır kontrolleriyle karşılar.

Araç Güvenlik Uygun senaryo
Dizi Yüksek Genel amaçlı veri
Span<T> Yüksek Hızlı dilimleme ve tamponlar
stackalloc Orta Küçük, kısa ömürlü tampon
Ham işaretçi Düşük Yerel API ve özel optimizasyon

Unsafe kod otomatik olarak daha hızlı değildir. JIT derleyicisi güvenli kodu zaten güçlü biçimde optimize eder. Önce BenchmarkDotNet gibi araçlarla ölçüm yapılmalı, darboğaz kanıtlanmalı ve yalnızca kritik bölüm unsafe hâle getirilmelidir. Kural basit: Ölçmeden işaretçiye sarılmayın; kullandığınızda adresleri, sınırları ve nesne ömürlerini titizlikle yönetin.

Yorumlar