|
C#'ta Gösterici(Pointer) Kullanmak - I |
|
Gönderiliyor lütfen bekleyin... |
|
|
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.
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.
Kaynaklar:
C# and The .NET Platform (Andrew Troelsen)
Professional C# 2nd Edition (Wrox)
MSDN Kütüphanesi
Makale:
C#'ta Gösterici(Pointer) Kullanmak - I C#, Visual C# ve .NET Sefer Algan
|
|
|
-
-
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
|
|