Loading...

7 Haziran 2007 Perşembe

Göstericiler(Pointers) Kullanmak || C#

Göstericiler(Pointer) alt seviye programlama için olmazsa olmaz yapılardır. Göstericiler nesnelerin bellekte tutuldukları adresleri saklayan veri yapılarıdır. Bu makalede C#'ta kullanımı çok fazla gerekli olmayan göstericilerin nasıl kulanıldıklarını inceleyeceğiz. Bu yazı göstericiler hakkında temel bilgilere sahip olduğunuzu varsaymaktadır.

.NET' in altyapısında gösterici kavramı sıklıkla kullanılırken göstericilerin açıkca kullanımı programcılar için gizlenmitir. Bunun nedeni gösterici kullanımının programlardaki görülemeyen hatalara sıkça yol açabilmesidir. Özellikle dili yeni öğrenenler için gösterici hataları içinden çıkılmaz bir hale gelebilmektedir. C#'ta göstericiler yerine referans değişkenleri mevcuttur. Referanslar heap bellek bölgesindeki nesnelerin başlangıç adresini tutar. Ancak bu adresin değerine kesinlikle ulaşamayız. Oysa C ve C++ dillerinde stack bölgesindeki değişkenlerin de adreslerine erişbilmemiz mümkündür. Üstelik değişkenlerin adreslerini örneğin 0x965474 şeklinde elde etmemiz bile mümkündür. C ve C++ programcılarına pek yabancı gelmeyecektir bunlar, ancak programlama dünyasına C# ile giren biri için göstericilere neden ihtiyaç duyabileceğimiz pek anlamlı gelmeyebilir. Şunu da söyeleyelim ki çok istisnai durumlar dışında göstericilere ihtiyacımız olmayacak, peki bu istisna durumlar nelerdir?

* Geriye Uyumluluk(Backward Compatibility) : COM ve WinAPI'deki fonksiyonlar gibi sık sık gösterici kullanan fonksiyonları C# ile programlarımızdan çağırabilmek için parametre olarak gösterici alan fonksiyonlara gösterici göndermemiz gerekebilir. Eğer C# ta gösterici kullanımına izin verilmemiş olsaydı tür uyumsuzluğu yüzünden gösterici kullanan eski COM ve DLL'lere erişebilmemiz mümkün olamazdı.

* Performans : C ve C++ dillerinin günümüze kadar çok popüler olmasının altında yatan nedenlerden biri de belleğe direkt erişimi sağladığı içinperformansın inanılmaz derecede yükselmesidir. Bizim için performansın çok önemli olduğu yerlerde gösterici kullanmamızdan daha doğal birşey olamaz.

* Alt Seviye İşlemler : Donanım arayüzleri ile direkt bir ilişki içerisinde olacak programlarda göstericilerin kullanımı mecburi gibidir. Bazen de belleğe kullanıcıların direkt erişebilmesi gereken programlar olabilir. Bu durumlarda da göstericilerden faydalanabiliriz.

Bütün bunların yanında gösterici kullanmanın bir çok sıkıntıyıda beraberinde getireceği kesindir. Öncelikle kullanımının zor olması ve kestirilmesi zor olan hatalara yol açabilmesi bu sıkıntıların en başında gelenidir. Zaten C#'ta gösterici kullanacağımız zaman kodu unsafe(güvensiz) anahtar sözcüğü ile işaretlememiz gerekir. Aksi halde program derlenemeyecektir. Normal bir metot içinde gösterici kullanımı yasaklanmıştır.

Şimdi de unsafe anahtar sözcüğünün kullanımına örnekler verelim.

1-) unsafe olarak işaretlenen sınıfların bütün metotlarında gösterici kullanabiliriz.

unsafe class Sınıf
{

}

2-) Normal bir metot içinde herhangi bir bloğu unsafe olarak aşağıdaki gibi işaretleyip dilediğimiz gibi gösterici kullanabiliriz. unsafe bloklarının dışında ise gösterici kullanamayız.

int NormalMetot(int a, string str)
{
unsafe
{

}
}

3-) Normal bir metodu unsafe olarak işaretleyip sadece o metodun içinde de gösterici kullanabiliriz.

unsafe int NormalMetot(int a, string str)
{

}

4-) Bir sınıfın üye değişkenlerinden biri unsafe olarak işaretlenip gösterici olarak bildirilebilir. Ancak bir metot içerisinde yerel bir gösterici tanımlanamaz. Yerel bir gösterici tanımlamak için unsafe olarak işaretlenmiş metod yada blok kullanılır.

class Sınıf
{
unsafe char *ptr;
}

Gösterici Tanımlama ve Gösterici Operatörleri

Göstericiler aşağıdaki gibi tanımlanabilir.

char* ptr1, ptr2;
int* ptr3;

ptr1 ve ptr2 char türden bir gösterici iken ptr3 int türden bir göstericdir. Bir göstericide iki bileşen vardır. Bu bileşenlerden birincisi adres bileşenidir. Adres bileşeni nesnenin bellekte bulunduğu başlangıç adresidir. İkinci bileşen ise tür bileşenidir. Bu bileşen ise ilgili adresteki nesneye ulaşmak istediğimizde bellekten ne kadarlık bilgili okunacağını sağlar. Örneğin int türden bir adresteki bilgiyi okumak istediğimizde 4 byte'lık bir bilgi okunacaktır, aynı şekilde char türden bir gösterici ise 2 byte'lık bir bilgi okunacaktır.

& operatörü

Adres operatörü olarak bilinen bu operatör değişkenlerin veya nesnelerin bellekte bulundukları adresleri elde etmek için kullanılır. Bu operatör hangi tür değişkenle kullunılırsa o türden bir gösterici üretilir.

* operatörü

İçerik operatörü olan *, bir adresteki bilgileri elde etmek için kullanılır. Aşağıdaki programda bu iki operatörün kullanımına bir örnek verilmiştir.

using System;

class Class1
{
unsafe static void Main()
{
int* ptr1;
char* ptr2;

int a = 50;
ptr1 = &a;
int Adres = (int)ptr1;
Console.WriteLine("{0:X}",Adres);

char ch = 'A';
ptr2 = &ch;
*ptr2 = 'B';
Console.WriteLine(ch);
}
}

Bu programı derleyebilmek için komut satırı derleyicisene programın içinde unsafe bloğunun olduğunu belirtmemiz gerekir. Bunun için komut satırına

csc /unsafe KaynakKod.cs

yada

csc -unsafe KaynakKod.cs

yazarak programı derleyebilirsiniz. Eğer Visual Studio.NET kullanıyorsanız projeye sağ tıklayıp proje özelliklerine gelip Build kısmından "Allow unsafe code blocks" kısmını true olacak şekilde değiştirin.

Programı çalıştırdığınızda ekrana a değişkeninin adresi ve B karekteri yazılacaktır. Adres bilgisini ekrana yazdırmadan önce göstericide tutulan adresi normal bir türe nasıl dönüştürdüğümüze dikkat edin. Bunu yapmamızın sebebi Consol.WriteLine() metodunun gösterici parametres alan versiyonunun olmamasıdır.

C#'ta tür güvenliğinin ne kadar önemli olduğunu vurgulamak için aşağıdaki deyimleri örnek verebiliriz.

int* ptr1;
*ptr1 = 50;

Bu deyimleri içeren bir program derlenemeyecektir. Çünkü ptr1 göstericisinde hangi adresin tutulduğu belli değildir. Bu yüzden adresi belli olmayan bellek bölgesine bir değer yerleştirmek imkansızdır. C ve C++ dillerinde bu kullanım tamamen geçerlidir. Ama gelin bir de bunun sakıncasına bir göz atalım. ptr1 göstericisi tanımlandığında ptr1 de rastgele bir adres değeri bulunmaktadır. Bu adreste rastgele bir adres olduğu için o an bellekte çalışan kendi programımızdaki bir değişkenin adresi bile olabilir. Yada sistemde çalışan başka bir prosesteki elemanların adresleri olabilir. Kaynağını bilmediğimiz bir adresteki değeri * operatörü ile değiştirdiğimizde hiç tahmin edemeyeceğimiz çalışma zamanı hataları alabiliriz. İşin kötüsü adresler rastgele olduğu için programımızın test aşamasında bu hatalar oluşmayabilir. Adresler nasıl rastgele ise hataların oluşması da rastgeledir. Programımız piyasada iken böyle bir hatanın farkına varılması iş maliyetlerini ne kadar artırdığını siz tahmin edin artık. Bütün bu dezavantajlar göstericilerin asla gereksiz olduğu anlamına gelmemelidir. Sadece göstericileri kullanırken daha dikkatli davranmamız gerektiğinin göstergesidir.

Göstericiler arasında tür dönüşümleri mümkündür. Örneğin int türden bir gösterici char türden bir göstericiye aşağıdaki gibi dönüştürülebilir.

char* ptr1
int* ptr2;

ptr1 = (char*)ptr2

Aynı şekilde bir gösterici de tamsayı türlerine dönüştürülebilir. Adresler tam sayı türünden birer sayı oldukları için bunu yapabilmemiz son derece normal bir durumdur. Bir önceki çalışan programda da Console.WriteLine() metodu ile ekrana yazmak istediğimiz nesnenin adresini tamsayı türlerinden birini çevirdiğimizi hatırlayın. Örneğin aşağıdaki deyimleri içeren bir program derlenemeyecektir.

char* ptr1
char ch = 'A';
ptr1 = ch;

Console.WriteLine(ptr1)


Göstericilerle ilgili diğer önemli yapı ise void göstericilerdir. void olan göstericilerde tür bilgisi saklanmamaktadır. void göstericilere herhangi bir türden gösterici atanabilir. void göstericiler daha çok eskiden yazılmış API fonksiyonlarında void parametre alan fonksiyoları programlarımız içerisinden çağırmak için kullanılır. void göstericilerin kullanımına aşağıda bir örnek verilmiştir.

int* ptr1;
void * x;
x = ptr1;

sizeof operatörü

sizeof operatörü temel türlerin ve yapıların bellekte ne kadar alan kapladıklarını verir. Örneğin sizeof(int) = 4, sizeof(char) = 2 ' dir. sizeof operatörünü sınıflar için kullanamayız ancak tanımlayacağımız yapılar için kullanabiliriz.

Bu yazının sonuna geldik. Bir sonraki yazımızda yapı göstericileri tanımlamayı sınıflar ile göstericiler arasındaki ilişkiyi, gösterici aritmetiğini inceleyip örnek bir gösterici uygulaması yapacağız.

Hiç yorum yok:

 
eXTReMe Tracker
Sayfa Bloggoayrılık yazılarıoyunlarkurye web tasarımı broşürlük dizi izle dizi izleKombi Tesisat Radyo DinleChat paysafe paysafe kartSohbet arkadaş travestitravesti travesti travesti