C#'ta göstericilerin kullanımına yönelik ilk yazıda göstericilere giriş yapmıştık, C#'ta göstericilerin kullanımını 3 yazılık bir seri halinde anlatmayı düşündüm. Bu yazıda gösterici aritmetiğini ve fixed anahtar sözcüğünün kullanımını öğreneceğiz.
Göstericilerin adres bilesenlerine sabit tamsayi degerleri ekleyebiliriz, ayni sekilde göstericilerin adres bileseninden sabit bir tamsayi degerini çikarabiliriz. Ancak göstericilere uygulanan bu toplama ve çikarma islemleri biraz farklidir. Göstericilere sabit degerlerin eklenmesi yada bir degerin göstericiden çikarilmasi göstericideki tür bileseni ile yakindan ilgilidir. Bir göstericinin degerini bir artirmak göstericinin adres bilesenini, göstericinin türünün içerdigi byte sayisi kadar artirmak demektir. Ayni kural çikarma islemi içinde geçerlidir. Örnegin int türünden bir gösterici ile 1 sayisini toplamak göstericinin adres bilesenini 4 artirmak anlamina gelir. Çünkü int türü 4 byte büyüklügündedir. Ayni sekilde int türden bir göstericiden 1 sayisini çikarmak göstericinin adres bilesenini 4 eksiltmek anlamina gelir. Göstericilerle yapilan bu tür aritmetik islemlerin tamamina gösterici aritmetigi denilmektedir.
Gösterici aritmetigini daha yakindan görmek için asagidaki programi yazin ve sonucunu inceleyin.
|
using System;
class Gosterici
{
unsafe static void Main()
{
int* ptr1 = (int*)500;
char* ptr2 = (char*)500;
double* ptr3 = (double*)500;
byte* ptr4 = (byte*)500;
ptr1 += 2;
ptr2 += 5;
ptr3 += 2;
ptr4 += 6;
Console.WriteLine((uint)ptr1);
Console.WriteLine((uint)ptr2);
Console.WriteLine((uint)ptr3);
Console.WriteLine((uint)ptr4);
}
}
|
Programi /unsafe argümani ile derleyip çalistirdigimizda asagidaki ekran görüntüsünüz elde ederiz. Programda Main() metodunun unsafe olarak isaretlendigine dikkat edin.
508
510
516
506
Programin çiktisindan da görüldügü üzere int türden bir göstericiye 2 sayisini eklemek göstericinin adres bilesenini 2*4=8 kadar artirmistir. Ayni sekilde char türden bir göstericiye 5 degerini eklemek göstericinin adres bilesenini 5*2 =10 kadar artirmistir. Toplama yerine çikarma islemi yapilmis olsaydi bu sefer ayni oranda adres bileseni eksiltimis olacakti.
Göstericiler üzerinde sadece tamsayilarla aritmetik islemler yapilabilir. Göstericiler ile asagidaki aritmetik operatörleri kullanabiliriz.
+ , - , -- , ++ , -=, +=
void göstericilerde herhangi bir tür bilgisi olmadigi için bu tür göstericiler üzerinde aritmetik islemler yapilamaz. Çünkü void türünden bir göstericiye örnegin 1 eklemek istedigimizde göstericinin adres bileseninin kaç byte ötelenecegi belli degildir.
Göstericiler üzerinde yapilabilecek diger önemli islemde iki göstericinin birbirinden çikarilmasidir. Gösterici türleri ayni olmak sartiyla iki göstericiyi birbirinden çikarabiliriz. Ancak iki göstericinin çikarilmasi sonucunda üretilen deger bir gösterici türü degildir. Iki gösterici arasindaki fark, adres bilesenlerinin sayisal farkinin gösterici türlerinin büyüklügünden kaç adet byte miktari edecegidir. Diger bir deyisle adres bilesenlerinin sayisal farki alinip gösterici türünün byte miktarina göre bir deger belirlenir. Iki göstericinin farki long türden bir deger üretir. Iki göstericinin farkina örnek verecek olursak, int türden 5008 adres ile int türden 5000 adresinin farki (5008-5000) % sizeof(int) tir. Yani sonuç long türden 2 dir. Asagidaki programi yazarak sonucu görebilirsiniz.
|
using System;
class Gosterici
{
unsafe static void Main()
{
int* ptr1 = (int*)500;
int* ptr2 = (int*)508;
long fark=ptr2 - ptr1;
Console.WriteLine(fark);
}
}
|
Diger bir ilginç nokta iki göstericinin adres bilesenlerinin farki gösterici türlerinin büyüklügünün tam kati olmadiginda görülür. Örnegin ptr2 göstericisini tanimlanmasini
seklinde degistirdiginizde bu sefer ekrana 1 yazdigini görürsünüz. Burdan çikarmamiz gereken sonuç iki göstericinin farki adres bilesenlerinin sayisal farkinin olmamasidir.
Dikkat: Iki göstericinin farki long türden bir deger üretir. Bu yüzden iki göstericinin farki açikca bir tür dönüsümü yapilmadikça long türünden küçük türden olan degiskenlere atanamaz.
Göstericiler ile kullanilabilecek diger operatörler ise ==, < ve > gibi karsilastirma operatörleridir. Bu operatörler iki göstericinin adres bilesenini karsilastirip ture yada false degeri üretirler. Karsilastirma operatörleri göstericiler için çok istisnai durumlar disinda anlamli degildir. Bu istisna durumlardan biri göstericileri kullanarak dizi islemleri yaptigimizda görülür.
fixed Anahtar Sözcügü
Bildiginiz gibi C#' ta tanimladigimiz referans degiskenleri heap bellek bölgesindeki adresler temsil ederler. Ancak biz adresler yerine nesnenin ismini kullaniriz. Gereksiz bilgi toplayicis(garbage collector) bellek optimizasyonui açisindan heap bellek bölgesindeki nesnelerin yerlerini her an degistirebilir. Bu yer degisiminden bizim haberimiz olmaz, çünkü nesnenin yeri degistigi anda bu nesneye referans olan stack bellek bölgesindeki degiskenin adres bileseni de degistirilir. Dolayisiyla biz ayni referans ile farkli bellek bölgesini istegimiz disinda kullanmis oluruz. Ancak bazi durumlarda gereksiz nesne toplayicisina bir nesnenin adresini degistirmemesi için ikna etmek durumunda kaliriz. Bu, özellikle sinif nesnelerinin üye elemanlarindan birinin adresi ile islem yapmamiz gerektigi durumlarda karsimiza çikar. Bir degiskenin adresinin belirlenen bir faaliyet alani boyuncu degismeden kalmasi için bunu gereksiz nesne toplayicisina bildirmemiz gerekir. Bunun için fixed anahtar sözcügü kullanilir.
Zaten fixed anahtar sözcügünü kullanmadan referans türünden nesnelerin üye elemanlarinin adreslerini elde etmemiz mümkün degildir. Üye elemanlarinin adreslerini elde edemedigimiz bu tür nesnelere managed type(yönetilen tip) denilmektedir. Buna göre siniflar managed type kapsamina girmektedir.
Asagidaki programda ManagedType isimli sinifin int türden olan x elemaninin adresi bir göstericiye atanmak isteniyor.
|
using System;
class ManagedType
{
public int x;
public ManagedType(int x)
{
this.x = x;
}
}
class Gosterici
{
unsafe static void Main()
{
ManagedType mt = new ManagedType(5);
int* ptr1 = &(mt.x);
}
}
|
ManagedType sinifinin x elemani deger tipi olmasina ragmen mt nesnesi üzerinden x degiskeninin adresi elde edilememektedir. Çünkü x degiskeninin adresi gereksiz nesne toplayicisi tarafindan her an degistirilebilir. Eger yukaridaki kod geçerli olmus olsaydi x degiskeninin adresi degistigi anda ptr1 göstericisi nereye ait oldugu bilinmeyen bir adres bilgisi tasiyor olacakti. x degiskeninin bir blok içerisinde sabit adreste olmasini istiyorsak asagidaki gibi fixed anahtar sözcügünü kullanmaliyiz.
|
using System;
class ManagedType
{
public int x;
public ManagedType(int x)
{
this.x = x;
}
}
class Gosterici
{
unsafe static void Main()
{
ManagedType mt = new ManagedType(5);
fixed(int* ptr1 = &(mt.x))
{
//x'in adresi bu blokta asla degismez.
}
}
}
|
Yukaridaki fixed ile isaretlenmis blokta x'in adresinin degismeyecegi garanti altina alinmistir. Birden fazla degiskeni fixed olarak isaretlemek için asagidaki gibi bir kullanim geçerli kilinmistir.
|
ManagedType mt1 = new ManagedType(5);
ManagedType mt2 = new ManagedType(5);
fixed(int* ptr1 = &(mt1.x))
fixed(int* ptr2 = &(mt2.x))
{
//x'in adresi bu blokta asla degismez.
}
|
Öte yandan bir fixed bildirimi içinde adreslerinin degismesini istemedigimiz elemanlari virgül ile ayirarak asagidaki gibi de bildirebiliriz.
|
ManagedType mt1 = new ManagedType(5);
ManagedType mt2 = new ManagedType(5);
fixed(int* ptr1 = &(mt2.x), ptr2 = &(mt2.x))
{
//x'in adresi bu blokta asla degismez.
}
|
Göstericilerle ilgili son yazıda, yapı göstericileri, göstericiler ile dizi işlemleri ve stackalloc ile dinamik alan tahsisatı yapma gibi konuları inceleyeceğiz.
Kaynak : csharpnedir.com
|