Bu site emekli olmuştur. Arşiv amaçlı olarak BT AKADEMİ sponsorluğunda yayın hayatına devam etmektedir.




C#nedir?com
 
YAZAR HAKKINDA
Sefer Algan
Sefer Algan
http://www.seferalgan.com
İletişme geçmek için tıklayın.
71 Makalesi yayınlanmakta.
Yazar hakkında detaylı bilgi için tıklayın.
Yayınlanan diğer makaleleri için tıklayın.
İlgili etiketler:  C# / VC#/.NET Sefer Algan
 
YAZI HAKKINDA
Türü : Makale
Serbest Köşede C#nedir?com üyelerinin hazırladıkları yazılar yayınlanır. Bu yazılar editör incelemesine girmeden yayınlanır.
Seviyesi : Başlangıç
Kategori : C# / VC#/.NET
Yayınlanma Tarihi : 13.4.2003
Okunma Sayısı : 20495
Yorum Sayısı : 1     yorum yaz
Site İçi AramaSİTE İÇİ ARAMA
Üye Girişini AçÜye GİRİŞİ
Üye girişi için tıklayın.
Kullanıcı Adı
Şifre
 
Beni her zaman hatırla
Bir hafta boyunca kullanıcı bilgilerinizi kullanıcı çıkışı yapana kadar hatırlar. (Paylaşılan bilgisayarlarda önerilmez.)
 
Şifremi / Kullanıcı Adımı unuttum.
 
.net TV RSS Serbest KÖŞE (?)
Serbest Köşede C#nedir?com üyelerinin hazırladıkları yazılar yayınlanır. Bu yazılar editör incelemesine girmeden yayınlanır.
emre TAŞ
Silindi
emre TAŞ
yazının devamı >
emre TAŞ
silindi
emre TAŞ
yazının devamı >
emre TAŞ
silindi
emre TAŞ
yazının devamı >
emre TAŞ
silindi
emre TAŞ
yazının devamı >
emre TAŞ
silindi
emre TAŞ
yazının devamı >
Makale Gönder Bende Yazmak İstiyorum
.net TV RSSBlogroll
Turhal Temizer
Conda install environment.yml Package 10.8.2022
Turhal Temizer
Mac OS/X Removing CUDA 10.8.2022
  Diğer Herşey
Sponsorlar
BT Akademi
Medya Portakal
Video Hosting Sponsoru
Csharpnedir.com bir Ineta üyesidir
Uzman Abi
Her Yönüyle C# - Sefer Algan
C#'ta Gösterici(Pointer) Kullanmak - III
 
Kapat
Sayfayı Yazdır Sık Kullanılanlara Ekle Arkadaşıma Gönder MySpace Del.Ico.Us Digg Facebook Google Mixx Reddit StumbleUpon
C#'ta göstericilerin kullanımı ile ilgili yazı dizisinin son bölümü olan bu yazıda gösericiler ile dizi islemlerinin nasıl yapıldıgi, stackalloc ıle dinamik bellek tahsısatının yapılmasını ve son olarak yapı(struct) göstericilerinin kullanılışını inceleyeceğiz.

Göstericiler ile Dizi İşlemleri
C#'ta tanımladıgımız diziler System.Array sınıfi türündendir. Yani bütün diziler managed type kapsamına girerler. Bu yüzden tanımladıgımız bir dizinin herhangi bir elemanının adresini fixed bloğu kullanmadan bir göstericiye atayamayız. Bu yüzden göstericiler ile dizi islemleri yaparken ya fixed bloklari kullanıp gereksiz nesne toplayıcısını uyarmalıyız yada stackalloc anahtar sözcüğünü kullanarak kendimiz unmanaged type(yönetilemeyen tip) dizileri olusturmalıyız. Her iki durumuda birazdan inceleyeceğiz.

Bildiğiniz gibi dizi elemanları bellekte ardışıl bulunur. O halde bir dizinin elemanlarını elde etmek için dizinin ilk elemanının adresini ve dizinin boyutunu bilmemiz yeterlidir. System.Array sınıfı dizilerinin elemanlarina göstericiler yardimiyla rahatlıkla ulasabiliriz. Bunun için ilk olarak fixed ile isaretlenmis bir blok içerisinde dizinin ilk elemanının adresini bir göstericiye atamalıyız. Ardından göstericinin degerini bir döngü içerisinde birer birer artırdıgımızda her döngüde dizinin bir sonraki elemanina ulaşmış oluruz. Dizilere bu sekilde erisebilmemizi sağlayan ise gösterici aritmetiğidir. Tabi dizilerin elemanlarının bellekte ardışıl bulunması da bu işlemi bu şekilde yapmamızı sağlayan ilk etkendir.

Şimdi yönetilen türden(managed) bir dizinin elemanlarına nasıl eriştiğimizi bir örnek üzerinde inceleyelim.


using System;

class Gosterici
{
   unsafe static void Main()
   {

      int[] a = {1,2,3,4};

      fixed(int* ptr = &a[0])
      {
         for(int i=0; i             Console.WriteLine(*(ptr+i));
      }
   }
}

Programı derlediğinizde dizinin elemanlarının

1
2
3
4

seklinde ekrana yazdırıldığını görürsünüz. (programı derlerken /unsafe argümanını kullanmayı unutmayın). Programın kritik noktası


fixed(int* ptr = &a[0])

satırı ile dizinin ilk elemanının adresinin elde edilmesidir. Zira dizinin diğer elemanlarıda sırayla ilk elemandan itibaren her eleman için adres değeri 4 byte artacak şeklindedir. Diger önemli nokta ise for döngüsü içindeki

*(ptr+i)

ifadesidir. Bu ifade her döngüde ptr göstericisinin adres bileşeni, döngü degişkeni kadar artırılıyor, sözgelimi döngü degişkeni 1 ise ptr'nin adres bileşeni 4 artırılıyor. Bu da dizinin ikinci elemanının bellekte bulunduğu adrestir. İçerik operatörü ile bu adrese erişildiğinde ise dizinin elemanı elde edilmis olur.

Gösterici dizileri ile ilgili diğer önemli nokta göstericilerin indeksleyici gibi kullanılabilmesidir. Örneğin yukarıdaki örnekte bulunan

*(ptr+i)

ifadesini

ptr[i]

şeklinde degiştirebiliriz. Burdan aşağıdaki eşitlikleri çıkarabiliriz.


*(ptr+0) == ptr[0]
*(ptr+1) == ptr[1]
*(ptr+2) == ptr[2]
*(ptr+3) == ptr[3]

Dikkat: ptr[i] bir nesne belirtirken (ptr+i) bir adres belirtir.

Dizilerin isimleri aslinda dizilerin ilk elemanının adresini temsil etmektedir. Örneğin aşagıdaki programda bir dizinin ilk elemanının adresi ile dizinin ismi int türden bir göstericiye atanıyor. Bu iki göstericinin adres bileşenleri yazdırıldıgında sonucun aynı olduğu görülmektedir.


using System;

class Gosterici
{
   unsafe static void Main()
   {
      int[] a = {1,2,3,4};

      fixed(int* ptr1 = a, ptr2 = &a[0])
      {
         Console.WriteLine((uint)ptr1);
         Console.WriteLine((uint)ptr2);
      }
   }
}

Programı derleyip çalıştırdığınızda ekrana alt alta iki tane aynı sayının yazıldığını görürsünüz.

Yönetilen(managed) tiplerle çalışmak her ne kadar kolay olsa da bazı performans eksiklikleri vardır. Örneğin bir System.Array dizisinin bir elemanına erişmek ile stack bölgesinde olusturacagımız bir dizinin elemanına ulaşmamız arasında zaman açısından büyük bir fark vardır. Bu yüzden yüksek performanslı dizilerle çalışmak için System.Array sınıfının dışında stack tabanlı diziler oluşturmamız gerekir. Stack tabanlı diziler yönetilemeyen dizilerdir. Bu yüzden bu tür dizileri kullanırken dikkatli olmalıyız. Çünkü her an bize tahsis edilmeyen bir bellek alanı üzerinde islem yapiyor olabiliriz. Ancak yönetilen dizilerde dizinin sınırlarını aşmak mümkün degildir. Hatırlarsanız bir dizinin sınırları aşılınca çalışma zamanında IndexOutOfRangeException istisnai durumu meydana geliyordu. Oysa stack tabanlı dizilerde dizinin sınırları belirli degildir ve tabiki dizinin sınırlarını aşmak kısıtlanmamıştır. Eğer dizinin sınırları aşılmışsa muhtemelen bu işlem bir hata sonucu yapılmıştır. Hiçbir programcı kendisine ait olmayan bir bellek alanında islem yapmamalıdır. Aksi halde sonuçlarına katlanması gerekir.

Stack tabanlı diziler stackalloc anahtar sözcüğü ile yapılır. stackalloc bize istediğimiz miktarda stack bellek bölgesinden alan tahsis eder. Ve tahsis edilen bu alanın başlangıç adresini geri döndürür. Dolayısıyla elimizde olan bu baslangıç adresi ile stackalloc ile bize ayrılmış olan bütün bellek bölgelerine erişebiliriz. stackalloc anahtar sözcüğünün kullanımı aşağıdaki gibidir.

int * dizi = stackalloc int[10];

Bu deyim ile stack bellek bölgesinde 10*sizeof(int) = 40 byte'lık bir alan programcının kullanmasi için tahsis edilir. Bu alan, dizinin faaliyet alanı bitinceye kadar bizim emrimizdedir. Tahsis edilen bu 40 byte büyüklüğündeki bellek alanının ilk byte'ının adresi ise int türden gösterici olan dizi elemanına aktarılır. Dolayısyla dizi göstericisi ile içerik operatörünü kullandığımızda bize ayrılan 10 int'lik alanın ilk elemanına erişmiş oluruz.

! stackalloc ile tahsis edilen bellek alanlarının ardışıl olması garanti altına alınmıştır.

stackalloc ile alan tahsisatı yapılır. Ancak alan tahsisatı yapılan bellek bölgesi ile ilgili hiçbir islem yapılmaz. Yani yukaridaki deyim ile, içinde tamamen rastgele değerlerin bulundugu 40 byte'lık bir alanımız olur. Bu alandaki değerlerin rastgele degerler olduğunu görmek için asagidaki programı yazın.

using System;

class Gosterici
{
   unsafe static void Main()
   {
      int * dizi = stackalloc int[10];

      for(int i=0; i<10;++i)
         Console.WriteLine("*(dizi+{0}) = {1}",i,dizi[i]); }
   }
}

! Yukarıdaki programda bir gösterici ile bellekteki ardışıl bölgelere indeksleyici operatörü ile nasıl eriştiğimize dikkat edin.

Programı /unsafe argümani ile beraber derleyip çalıştırdıktan sonra aşağıdaki ekran görüntüsünü elde etmeniz gerekir. Tabi bu değerler rastgele oldugu için sizdeki görüntü tamamen farklı olacaktır. Rastgele değerden kasıt çalışma zamanında Random gibi bir sınıfin kullanılıp rastgele bir sayı üretilmesi değildir. Burdaki sayılar daha önce çalışmiş olan programlardan kalan çöp değerlerdir.



C:\Programlar\StackAlloc
*(dizi + 0) = 0
*(dizi + 1) = 1244236
*(dizi + 2) = 1243328
*(dizi + 3) = 1350496
*(dizi + 4) = 124334
*(dizi + 5) = 1287174
*(dizi + 6) = 1243328
*(dizi + 7) = 0
*(dizi + 8) = 0
*(dizi + 9) = 1243404

C:\Programlar\StackAlloc


Console.WriteLine("*(dizi+{0}) = {1}",i,dizi[i]);

satırındaki

dizi[i]

yerine

*(dizi + i)

yazmamız herhangi birşeyi değiştirmezdi. Daha önce bu iki kullanımın eşdeğer olduğunu belirtmiştik.

Stack tabanli dizilerle ilgili bilinmesi gereken en önemli nokta dizinin sınırlarının aşılması ile ilgilidir. Daha önceden de denildiği gibi stack tabanli dizilerin sınırları aşıldığında herhangi bir uyarı verilmez. Elbetteki program başarıyla derlenir ancak çalımma zamanında bize ait olmayan bir adresin içeriğini degiştirmiş oluruz ki bu da bir programcının başina gelebilecek en tehlikeli durumdur. Örneğin aşağidaki programda stackalloc ile 10 int türünden nesnelik alan tahsis edilmektedir. Buna rağmen istediğimiz kadar alanı kullanabiliyoruz.


using System;

class Gosterici
{
   unsafe static void Main()
   {
       int * dizi = stackalloc int[10];

      for(int i=0; i<50;++i)
          *(dizi+i) = i;
   }
}

stackalloc ile oluşturacağımız dizilerin boyutu derleme zamanında bilinmek zorunda değildir. Örneğin çalışma zamanında kullanıcının belirlediği sayıda elemana sahip olan bir ardışıl bellek bölgesi aşagıdaki programda oldugu gibi tahsis edilebilir.

using System;

class Gosterici
{
   unsafe static void Main()
   {
      Console.Write("Dizi boyutu gir: ");
      uint boyut=0;

      try
      {
         boyut = Convert.ToUInt32(Console.ReadLine());
      }
      catch(FormatException e)
      {
         Console.WriteLine(e.Message);
      }

      int * dizi = stackalloc int[(int)10];

      for(int i=0; i<(int)boyut; ++i)
      {
         *(dizi+i) = i;
         Console.WriteLine(dizi[i]);
      }
   }
}

Bu program ile çalışma zamanında 7 elemanlı stack tabanlı bir dizinin oluşturulduğunu aşağıdaki ekran görüntüsünden görebilirsiniz.



Yapı(Struct) Türünden Göstericiler

int,char,double gibi veri türleri aslında birer yapıdır. Bu konun başında bütün değer tipleri ile gösterici tanımlayabileceğimizi söylemiştik. C# temel veri türlerinin yanısıra kendi bildirdiğimiz yapılar da değer türündendir. O halde bir yapı göstericisi tanımlayabilmemiz doğal bir durumdur. Yapı göstericilerinin tanımlanması temel veri türlerinden gösterici tanımlama ile aynıdır. Ordaki kuralların tamamı yapılar içinde geçerlidir. Fakat yapı göstericisi tanımlamanın bir şartı vardır, oda yapının üye elemanlarının tamamının değer tipi olma zorunluluğudur. Örneğin aşağıdaki yapı göstericisi tanımlaması geçersizdir.


using System;

struct Yapi
{
   int x;
   char c;
   string s;

   public Yapi(int x,char c, string str)
   {
      this.x = x;
      this.c = c;
      this.s = str;
   }
}

class StackAlloc
{
   unsafe static void Main()
   {
      Yapi yapi = new Yapi(2,'a',"Deneme");

      Yapi* pYapi = &yapi;
   }
}

Yukarıda 'Yapi' türünden göstericinin tanımlanamamasının sebebi yapının yönetilen türden(managed type) bir üye elemanının bulunmasıdır. Bu üye elemanı da doğal olaral string türüdür. Yapı bildiriminden string türünü çıkarıp aşağıdaki gibi ilgili değişiklikleri yaptığımızda 'Yapi' türünden göstericileri tanımlayabiliriz.

using System;

struct Yapi
{
   int x;
   char c;


   public Yapi(int x,char c)
   {
      this.x = x;
      this.c = c;
   }
}

class StackAlloc
{
   unsafe static void Main()
   {
      Yapi yapi = new Yapi(2,'a');

      Yapi* pYapi = &yapi;
   }
}

Yapı göstericileri üzerinden yapı göstericisinin adresine ilişkin nesnelerin elemanlarına özel bir operatör olan -> operatörü ile erişebiliriz. Örneğin yukarıdaki programın Main() metodunu asağıdaki gibi değiştirdiğinizde ekrana yapı nesnesinin x ve c elemanları yazdırılacaktır. Tabi Yapi'nın üye alamanlarını public olarak değiştirmeniz gerekecektir. Çünkü ok operatörü ilede olsa ancak public olan elemanlara ulaşabiliriz.

unsafe static void Main()
{
   Yapi yapi = new Yapi(2,'a');

   Yapi* pYapi = &yapi;

   Console.WriteLine("yapi.x= " + pYapi->x);
   Console.WriteLine("yapi.c= " + pYapi->c);
}

Not: ' -> ' operatörüne ok operatörü de denilmektedir.

Yapının public olan elemanlarına ok operatörü yerine yapı nesnesinin içeriğini * operatörü ile elde edip nokta operatörü ile de ulaşabiliriz. Buna göre yukarıdaki Main() metodu ile asagidaki Main() metodu eşdeğerdir.

unsafe static void Main()
{
   Yapi yapi = new Yapi(2,'a');

   Yapi* pYapi = &yapi;

   Console.WriteLine("yapi.x= " + (*pYapi).x);
   Console.WriteLine("yapi.c= " + (*pYapi).c);
}

Her iki Main() metodunun ürettiği çıktı aynıdır.

Göstericilerin en çok kullanıldığı diğer bir uygulama alanı da karakter işlemleridir. Bir yazıyı karekter dizisi olarak temsil edip yazılar ile ilgili işlemler yapılabilir. C#' taki string türünün altında geçekleşen olaylarda zaten bundan ibarettir. Karakter dizileri ile ilgili en önemli nokta bir yazıyı char türden bir göstericiye atayabilmemizdir. Karekter dizileri olan stringlerdeki her bir karakter bellekte ardışıl bulunmaktadır. Dolayısıyla yazıdaki ilk karakterin adresini bildiğimizde yazıdaki bütün karakterlere erişebiliriz. C#'taki string türü yönetilen tip(managed type) oldugu için char türden bir göstericiye bir yazının ilk karekterinin adresini atamak için fixed anahtar sözcüğünü kullanmalıyız. Aşağıdaki programda bir yazının char türden göstericiye nasıl atandığını ve bu gösterici ile yazıdaki her karaktere ne şekilde erişildiğini görüyorsunuz.


using System;

   class KarakterDizisi
   {
      unsafe static void Main()
      {
         fixed(char* ptr = "Sefer Algan")
         {
            for(int i=0; ptr[i] != '\0'; ++i)
               Console.Write(ptr[i]);
         }
      }
}

Buradaki en önemli nokta ptr[i]' nin '\0' karakteri ile karşılaştırıldığı yerdir. Göstericiler ile bellekte char türünün büyüklügü kadar ilerlerken yazının nerede sonlandıgını bilemeyiz. Bunun için

char* ptr = "Sefer Algan"

deyimi ile belleğe yerlestirilen 'n' karakterinden sonra null değerini ifade eden '\0' karekter yerleştirilir. Bu karektere rastlanıldığı zaman yazının sonuna gelmiş bulunuyoruz.

Not: Göstericilerle ilgili yayınlanan bu makale dizisi yazmış olduğum ve yakında Pusula yayıncılıktan çıkacak olan C# kitabından alınmıştır.


Makale:
C#'ta Gösterici(Pointer) Kullanmak - III C#, Visual C# ve .NET Sefer Algan
  • Yazılan Yorumlar
  • Yorum Yaz
OCA
28
2007
Verdiğiniz bilgiler için teşekkürler. Yazdığınız makaleler ve kitapların türk yazılımcılarına çok faydalı olduğuna eminim. Kodda küçük bir hata var: Derleme zamanında boyutu bilinmeyen diziler için olan kod içerisindeki int * dizi = stackalloc int[(int)10] satırı int* dizi = stackalloc int[(int)boyut] olmalı. 10 bir önceki koddan kalmış olmalı.
Sayfalar : 1 
Yorum yazabilmek için üye girişi yapmalısınız. Üye girişi için tıklayın.
Üye değilseniz Üyel Ol linkine tıklayarak üyeliğinizi hemen başlatabilirisniz.
 
  • Bu Konuda Son 10
  • Eklenen Son 10
  • Bu Konuda Geçmiş 10
Bu Konuda Yazılmış Yazılmış 10 Makale Yükleniyor
Son Eklenen 10 Makale Yükleniyor
Bu Konuda Yazılmış Geçmiş Makaleler Yükleniyor