Yükleniyor...

C# Unsafe Kod ve Pointer Kullanımı

C#’ta unsafe kod ve pointer kullanımını öğrenin. Bellek adresleri, pointer işlemleri ve düşük seviye senaryolar örneklerle anlatılıyor.

C# dili güvenli (safe) bellek yönetimine sahip olsa da, bazı yüksek performans gerektiren senaryolarda doğrudan belleğe erişim gerekebilir. Unsafe kod bu durumlar için kullanılır. Unsafe bloklar, pointer (işaretçi) kullanarak bellek adreslerine doğrudan erişmenizi sağlar. Bu yaklaşım düşük seviye kontrol imkânı sunsa da dikkatli kullanılmalıdır; çünkü .NET’in garbage collector (GC) korumalarının dışındadır.


Unsafe Kod Nedir?

Unsafe anahtar kelimesi, C# derleyicisine kodun yönetilmeyen (unmanaged) bellekle çalışacağını belirtir. Bu tür kodlarda derleyici, bellek güvenliği kontrollerini yapmaz. Unsafe kodu kullanabilmek için proje derleme ayarlarında “Allow unsafe code” seçeneği aktif olmalıdır.


// .csproj dosyasına eklenmesi gereken özellik:
<PropertyGroup>
  <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

Pointer (İşaretçi) Nedir?

Pointer, bir değişkenin bellekteki adresini tutan değişkendir. C#’ta pointer’lar yalnızca unsafe bloklar içinde tanımlanabilir ve stack üzerinde kullanılır. * işareti pointer tanımı yapar, & operatörü ise bir değişkenin adresini alır.


using System;

class Program
{
    static unsafe void Main()
    {
        int x = 42;
        int* ptr = &x; // x'in bellekteki adresini al

        Console.WriteLine($"x değeri: {*ptr}");
        Console.WriteLine($"x adresi: {(ulong)ptr}");
    }
}

*ptr ifadesi “adresin işaret ettiği değeri oku” anlamına gelir. Bu örnekte hem değişkenin adresine hem değerine erişilir.


Pointer Üzerinden Değer Değiştirme

Pointer kullanarak bir değişkenin değerini doğrudan değiştirebilirsiniz. Bu işlem C/C++ dillerindeki pointer mantığıyla aynıdır.


unsafe
{
    int x = 10;
    int* p = &x;

    *p = 99; // pointer üzerinden x değerini değiştir
    Console.WriteLine($"x yeni değeri: {x}");
}

// Çıktı:
// x yeni değeri: 99

Pointer Arithmetic (İşaretçi Aritmetiği)

Pointer aritmetiği, pointer adreslerini artırarak dizilerde gezinmeyi sağlar. Ancak yalnızca int, byte, double gibi ilkel türlerde kullanılabilir.


unsafe
{
    int[] sayilar = { 10, 20, 30, 40 };
    fixed (int* p = sayilar)
    {
        for (int i = 0; i < sayilar.Length; i++)
            Console.WriteLine($"Adres: {(ulong)(p + i)}, Değer: {*(p + i)}");
    }
}

fixed anahtar kelimesi, dizi belleğini GC’nin taşınamayacağı şekilde sabitler. Böylece pointer her zaman doğru adresi gösterir.


fixed Anahtar Kelimesi

fixed, yönetilen bir nesneyi (örneğin dizi veya string) GC tarafından taşınamaz hale getirir. Bu, pointer kullanarak güvenli şekilde bellek adreslerine erişmeyi sağlar.


unsafe
{
    string mesaj = "Merhaba";
    fixed (char* p = mesaj)
    {
        for (int i = 0; i < mesaj.Length; i++)
            Console.Write(p[i]);
    }
}

Dikkat: fixed bloğu bittiğinde nesne yeniden taşınabilir hale gelir, bu yüzden pointer’ı bu blok dışında kullanmak güvenli değildir.


struct ve Pointer ile Bellek Erişimi

Pointer kullanarak struct’lar üzerinde de doğrudan belleğe erişim mümkündür. Bu, performans kritik durumlarda (ör. oyun motorları, grafik hesaplama, native interop) sık kullanılır.


using System;

struct Nokta
{
    public int X;
    public int Y;
}

class Program
{
    static unsafe void Main()
    {
        Nokta n = new Nokta { X = 5, Y = 10 };
        Nokta* ptr = &n;

        ptr->X = 20;
        Console.WriteLine($"Nokta: X={ptr->X}, Y={ptr->Y}");
    }
}

ptr->X ifadesi C/C++’taki struct pointer erişim biçimidir.


stackalloc ile Stack Üzerinde Bellek Ayırma

stackalloc, stack üzerinde doğrudan ham bellek ayırır. Bu bellek otomatik olarak scope bitiminde serbest bırakılır.


unsafe
{
    int* p = stackalloc int[5];
    for (int i = 0; i < 5; i++)
        p[i] = i * 2;

    for (int i = 0; i < 5; i++)
        Console.WriteLine(p[i]);
}

stackalloc hızlıdır ancak yalnızca küçük verilerde kullanılmalıdır. Büyük veriler için heap tahsisi (new) tercih edilmelidir.


Unsafe Kod Ne Zaman Kullanılır?


Performans Karşılaştırması

Unsafe kod ile doğrudan bellek erişimi, GC’yi atladığı için genellikle %30–50 oranında performans kazandırabilir. Ancak bu kazanç, hata riskinin artması pahasına gelir. Yanlış adres erişimi, uygulamanın çökmesine veya bellek bozulmasına neden olabilir.


Dikkat Edilmesi Gerekenler


Örnek: Görüntü Piksel Verisine Erişim

Aşağıdaki örnek, bir bitmap üzerindeki piksellere unsafe blokla doğrudan erişimi gösterir. Bu yöntem, piksel işleme (örneğin filtre uygulama) işlemlerini klasik API’lere göre çok daha hızlı yapar.


using System;
using System.Drawing;
using System.Drawing.Imaging;

class Program
{
    static unsafe void Main()
    {
        using Bitmap bmp = new Bitmap("image.png");

        Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
        BitmapData data = bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat);

        int bytesPerPixel = Image.GetPixelFormatSize(bmp.PixelFormat) / 8;
        byte* ilkPiksel = (byte*)data.Scan0;

        for (int y = 0; y < bmp.Height; y++)
        {
            byte* satir = ilkPiksel + (y * data.Stride);
            for (int x = 0; x < bmp.Width; x++)
            {
                byte* piksel = satir + (x * bytesPerPixel);
                piksel[0] = (byte)(255 - piksel[0]); // Mavi kanalı tersine çevir
                piksel[1] = (byte)(255 - piksel[1]); // Yeşil
                piksel[2] = (byte)(255 - piksel[2]); // Kırmızı
            }
        }

        bmp.UnlockBits(data);
        bmp.Save("output.png");
        Console.WriteLine("Görüntü ters renklere dönüştürüldü!");
    }
}

Bu örnek, yüksek performanslı pixel manipulation işlemlerinde unsafe kodun gücünü gösterir.


TL;DR

  • unsafe bloklar, C#’ta pointer ile doğrudan bellek erişimi sağlar.
  • fixed anahtar kelimesi, GC’nin nesneleri taşımamasını sağlar.
  • stackalloc, stack üzerinde hızlı, geçici bellek tahsisi yapar.
  • Performans kazanımı sağlar, ancak güvenlik riskleri yüksektir.
  • Yönetilmeyen kodlarla (C/C++) etkileşim veya düşük seviye bellek işlemlerinde kullanılır.

İlişkili Makaleler

C# Temel Veri Tipleri

C#’ta temel veri tipleri: sayısal, metinsel, mantıksal, nesne tabanlı ve nullable tiplerin kullanımı.