Derinlemesine yazılım eğitimleri için kanalımı takip edebilirsiniz...

C#’ta Gösterici(Pointer) – Gösterici Bildirimi ve Gösterici Operatörleri – IV

Merhaba,

Bir önceki içeriğimizde C#’ta Gösterici(Pointer) – Gösterici Bildirimi ve Gösterici Operatörleri – III başlığı altında göstericilerde, gösterici bildirimi üzerine giriş ve detay niteliğinde bir irdelemede bulunmuştuk. Bu makalemizde ise konumuzun ikinci kısmı olan gösterici operatörleri üzerine konuşuyor olacağız.

Yukarıda vermiş olduğum bir önceki makalenin içeriğinde olduğu gibi gösterici bildiriminde *(asterisk/yıldız) karakterini kullanıyorduk. Öncelikle * karakteri ile tanımlanmış bir göstericinin işaretleyeceği, bir değişken bellek adresini bize getirecek olan & operatörü üzerine değineceğiz.

&(Ampersand) Operatörü

& operatörü, adres operatörü olarak bilinmektedir. Değişkenlerin bellekteki adreslerini elde etmemizi sağlar. Daha doğrusu bu operatör hangi değişken üzerinde kullanılıyorsa o değişken tipinde bir pointer üretmektedir.

        static void Main(string[] args)
        {
            unsafe
            {
                int a = 3; //Değişken tanımlandı.
                int* apointer; //Pointer tanımlandı.
                apointer = &a; //a değişkeninin bellek adresi & operatörü ile elde edilip; bu adres, apointer göstericisi ile işaretlenmiştir.
            }
        }

Yukarıdaki kod bloğuna bakarsanız eğer & operatörü ile normal bir değişken olan “a” değişkeni “apointer” isimli gösterici ile işaretlenmiş bulunmaktadır. Aslında buradaki kullanımda & operatörü direkt olarak “a” değişkeninin tipinde bir pointer üretmektedir. Bunuda aşağıdaki ekran görüntüsünden daha net bir şekilde görebilirsiniz.

C#’ta Gösterici(Pointer) – Gösterici Bildirimi ve Gösterici Operatörleri – IV

Üretilen pointer “a” değişkeninin bellekteki adresini tutan bir pointerdır. Eee haliyle bu pointerı da “apointer” isimli pointer ile refere etmekteyiz.

Peki bu yaptığımız işlem neticesinde aslen neler oluyor?
Yukarıdaki yapmış olduğumuz çalışma neticesinde her ne kadar iki adet değişken tanımlamış gibi görünsede aslında bir adet değişken tanımlanmıştır. Aşağıdaki görselde de açıkça gösterildiği gibi “a” değişkeninin bellek adresi biryandan da “apointer” değişkeni tarafından işaretlendiği/gösterildiği için aslında iki referansta bellekteki aynı alanı refere etmektedir.
C#’ta Gösterici(Pointer) – Gösterici Bildirimi ve Gösterici Operatörleri – IV
Hatta birazdan pointerlar üzerinden gösterdikleri bellek adreslerine müdahale ettiğimizde değişkeninde değerinin değiştiğini göreceğiz. Çünkü ikiside aynı noktayı refere etmektedirler. Velhasıl oraya geldiğimizde size bu noktayı hatırlatacağım…

& operatörü struct, enum ve değer tipli değişkenler üzerinde kullanılabilir.

Aynen aşağıda olduğu gibi.

    struct MyStruct { }
    enum MyEnum { }
    class Program
    {
        static void Main(string[] args)
        {
            unsafe
            {
                #region Struct
                MyStruct ms;
                MyStruct* mspointer;
                mspointer = &ms;
                #endregion
                #region Enum
                MyEnum me;
                MyEnum* mepointer;
                mepointer = &me;
                #endregion
                #region Value Type
                int a = 3;
                int* apointer = &a;
                #endregion
            }
        }
    }

* Operatörü

Gösterici bildiriminde kullandığımız operatörle aynı karakter olan * operatörü iki farklı amaç için tasarlanmıştır. Birisi şuana kadar kullandığımız ve işte şimdi değindiğim gibi gösterici bildiriminde kullanılmaktayken bir diğeri ise bir pointerın işaretlediği adresteki bilgileri elde etmek yahut güncellemek yani bellek adresinin değer alanına direkt olarak müdahale etmek için kullanılır.

Eğer ki ikinci amaç doğrultusunda bu operatör kullanılırsa içerik operatörü olarak nitelendirilmektedir. * operatörü hangi tür pointer ile kullanılırsa bellekte müdahale edilecek bilgi miktarı o türün büyüklüğü ile aynı olacaktır.

        static void Main(string[] args)
        {
            unsafe
            {
                int a = 3; //Değişken tanımlandı.
                int* apointer;//Pointer tanımlandı.
                apointer = &a; //a değişkeninin bellek adresi apointer tarafından işaretlendi.

                *apointer = 10; //apointerın işaret ettiği bellekteki değer 10 olarak yenilendi.

                Console.WriteLine($"a değişkeni : {a}\napointer göstericisi : {*apointer}");
                //Burada ise apointerın işaret ettiği bellekteki değer elde edilmiştir.
                Console.Read();
            }
        }

Yukarıdaki kod bloğunu incelemeniz gayet yeterlidir. Gerekli açıklamaları ilgili kod satırlarının yanına yapmış bulunmaktayım. Dikkat ederseniz “*apointer” ifadesi ile “apointer” işaretçisindeki değere müdahale edebiliyor ya da değeri elde edebiliyoruz. Eğer ki projemizi derleyip çalıştırırsak aşağıdaki sonuçla karşılaşacağız.
C#’ta Gösterici(Pointer) – Gösterici Bildirimi ve Gösterici Operatörleri – IV.PNG

Şimdi burada makalemizin girişinde adresini vermiş olduğum önceki makalemizden sinyal verdiğim bir noktaya değinmek istiyorum.(Önceki makaleye buradan da erişebilirsiniz) Hani ilgili makalenin şöyle sonlarına doğru gösterici bildiriminde aşağıdaki gibi çoklu bir kullanımda bulunmuştuk.

            unsafe
            {
                int*** i;
            }

Heh işte bu kullanımla ilgili içerik operatörünü kullanırken dikkat etmemiz gerekmektedir. Daha doğrusu daha kompleks bir şekilde yaklaşım sergilememiz gerekecektir. Bunun nedenini şöyle izah edelim;

Yukarıdaki kullanımda “int” tipinde bir göstericiyi işaret eden (int***) bir başka göstericiyi (int***) işaret eden pointer (int***) temsil edilmektedir.

Ee haliyle bu pointerın işaretlediği bellek adresindeki değere müdahale etmek yahut ulaşmak için şu mantıkta hareket etmemiz gerekecektir;

i pointerının bellek adresini tuttuğu (***i) pointerın (***i) tekrardan bellek adresini tuttuğu pointerın (***i) değerine müdahale et.

Biraz kompleks gibi gelebilir ama aşağıdaki örnek konuyu birazdaha anlaşılır hale getirecektir.
C#’ta Gösterici(Pointer) – Gösterici Bildirimi ve Gösterici Operatörleri – IV.PNG
Dikkat ederseniz çoklu olan “ii” isimli pointerın içerik operatörü ile değerini talep ettiğimiz zaman hata vermektedir. Bunun sebebi yukarıdaki mantıkta da anlatmak istediğim gibi “ii” pointerı arka planda “i” pointerının bellek adresini işaretlemiş bulunmaktadır. Haliyle içerik operatörü “*ii” şeklinde kullanıldığında aslında “ii” pointerının değeri olan “i” pointerı getirilmektedir. Ee biliyoruz ki pointerlar direkt olarak çağrıldıklarında değer vermemektedir, içerik operatörünü kullanmamız gerekmektedir. Halbuki burada “*ii” kullanımı kullanıldığı yere “i” pointerını getirmektedir. Nihai sonucu elde etmek ve hatasız bir syntax yapısında kod yazmak istiyorsak “i” pointerına da aşağıdaki gibi bir adet içerik operatörü vermemiz gerekmektedir.

C#’ta Gösterici(Pointer) – Gösterici Bildirimi ve Gösterici Operatörleri – IV.PNG

İşte yukarıdaki gibi bir çalışma yaparsak sorunumuz ortadan kalkmaktadır. “**ii” kullanımında ilk “*” karakteri “ii” pointerının işaret ettiği bellek adresindeki değeri getirmesi için iken, ikinci “*” karakteri ise “ii” pointerının işaret ettiği bellek adresinden gelen “i” pointerının işaret ettiği bellek adresindeki değeri getirmesi içindir.

Aşağıdaki kod bloğunu incelerseniz eğer şöyle bir genellemeye varabiliriz; çoklu kombinasyon ne kadar derinse içerik operatörünün kullanımıda aynı orantıda derin olmalıdır.

        static void Main(string[] args)
        {
            unsafe
            {
                int a = 5;
                int* i = &a;
                int** ii = &i;
                int*** iii = ⅈ
                int**** iiii = &iii;
                int***** iiiii = &iiii;

                Console.WriteLine(*****iiiii);

                Console.Read();
            }
        }

Bu zor ve meşakkatli noktadan sonra içerik operatörümüzle ilgili anlatımımıza devam edelim.

*(içerik) operatörünün operandı bir adres olmak zorundadır.

Hiç dikkat ettiniz mi? bilmiyorum ama göstericilerin kullanım mantığı aynen referans tipli değişkenlerin kullanım mantığıyla oldukça benzerlik göstermektedir. Yani bir nesne birden fazla referans noktası tarafından işaretlense dahi tüm referanslar aynı nesneyi gösterdiği için nesne üzerindeki tüm değişiklikler referans farketmeksizin yansıtılacaktır.

Haliyle bu benzerliği aşağıdaki gibi kıyaslarsak daha net bir tablo ortaya çıkmaktadır.

Göstericiler Referans Tipli Değişkenler
C#’ta Gösterici(Pointer) – Gösterici Bildirimi ve Gösterici Operatörleri – IV.PNG C#’ta Gösterici(Pointer) – Gösterici Bildirimi ve Gösterici Operatörleri – IV
C#’ta Gösterici(Pointer) – Gösterici Bildirimi ve Gösterici Operatörleri – IV C#’ta Gösterici(Pointer) – Gösterici Bildirimi ve Gösterici Operatörleri – IV.PNG

Göstericilerle, referans tipli değişkenler arasındaki temel fark bellekte barındırılma alanlarının farklı olmasıdır. Göstericiler stack, referans tipli değişkenler ise heap kısmında burundurulmaktadır.

Şimdi ise arkadaşlar C# programlama dilinin tür güvenliğine ne kadar önem verdiğini resmedecek bir örnek üzerinden konumuza devam edeceğiz.

C# tür güvenliğini yüksek derecede önemseyen bir programlama dilidir. Hele hele belleğe direkt erişim söz konusu olduğu durumlarda bu önem kat be kat artmaktadır. Peki neden? Bu nedeni siz sorgularken bir yandan da aşağıdaki ekran görüntüsünü inceleyiniz.

C#’ta Gösterici(Pointer) – Gösterici Bildirimi ve Gösterici Operatörleri – IV.PNG
Dikkat ederseniz yukarıda yapılan çalışma derlenmemiş, hata vermiştir. Bu hatanın sebebi “pointer” isimli göstericinin hangi adresi tuttuğunun belli olmamasından kaynaklanmaktadır. Eğer ki bir pointera herhangi bir bellek adresi tutturmadan içerik operatörü(*) ile değer atamak yahut değerini okumak isterseniz yukarıdaki gibi program derlenmeyecektir.

Peki adresi belli olmayan bir pointera neden değer atayamıyoruz?
Aslında C yahut C++ programlama dillerinde çalışıyor olsaydık hata almaksızın bu tarz bir kullanımda sakıncalıda olsa bulunabilirdik. Herşeyden önce gelin bu tarz bir kullanımın neden sakıncalı olduğuna bakalım. Ardından kullanıp, kullanamama durumundaki değerlendirmeyi size bırakacağım.

“int* pointer” şeklinde gösterici tanımlandığı an ilgili gösterici rastgele bir adres değeri ile oluşturulmaktadır. Haliyle işaretlenen adres rastgele olduğu için o anki bellekte herhangi bir kritik adres yahut önemli ve erişilmesi sakıncalı değişken adresi olabilir. Velhasıl bilmediğimiz bir adresteki veriye müdahale etmek tahmin edemeyeceğimiz sonuçlara sebep olabilir.

İşin daha da kötüsü göstericiye verilen adres rastgele olduğu için yazılım test aşamasındayken olası hatalarla karşılaşmayabiliriz. Adreslerin rastgele olması gibi hatalarında rastgele olma ihtimalini göz ardı etmemek lazım. Aksi taktirde piyasaya sürülen yazılımdan ara ara gelen hayırsız dönütler ve pek iyimser olmayan kullanıcı yorumlarının yaratacağı telaşı ve mahcubiyeti düşünün.

Tabi ki de bu tarz dezavantajlar göstericileri anlamsızlaştırması ve değersizleştirmesi anlamına gelmemektedir. Sadece gösterici kullanırken daha dikkatli, sağlam adımlarla hareket edilmesi gerektiğini bilmeniz gerekmektedir.

İşte bu sebepten dolayı C# programlama dilinde göstericilere bellek adresi atamadan değer vermek ya da değerini elde etmek yasaklanmıştır.

sizeof Operatörü

sizeof operatörü struct, enum ve temel veri türlerinin ne kadar alan kapladıklarını verir. Sınıf yapıları için sizeof operatörü kullanılmaz. Genellikle struct yapıları için kullanılır. Bunun sebebi genellikle temel veri türlerinin değer aralıkları bilinmesidir.

    struct MyStruct { }
    enum MyEnum { }
    class Program
    {
        static void Main(string[] args)
        {
            int sizeTemelVeriTuru = sizeof(int);
            int sizeEnum = sizeof(MyEnum);

            unsafe
            {
                int sizeStruct = sizeof(MyStruct);
                Console.WriteLine($"Temel Veri Türü : {sizeTemelVeriTuru}");
                Console.WriteLine($"Enum : {sizeEnum}");
                Console.WriteLine($"Struct : {sizeStruct}");
            }
            Console.Read();
        }
    }

Burada dikkat etmeniz gereken nokta, sizeof operatörünü struct boyutunu ölçmek için kullacaksanız unsafe bloğu arasında bu işlemi yapmanız gerekmektedir.
C#’ta Gösterici(Pointer) – Gösterici Bildirimi ve Gösterici Operatörleri – IV.PNG
Ayriyetten struct içerisine property ekledikçe propertynin türü ne ise struct’ın boyutuda ona göre artmaktadır.

    struct MyStruct
    {
        public int x { get; set; } //+4
        public bool y { get; set; } //+4
        public int z; //+4
    }
    class Program
    {
        static void Main(string[] args)
        {
            unsafe
            {
                int sizeStruct = sizeof(MyStruct);
                Console.WriteLine($"Struct : {sizeStruct}");
            }
            Console.Read();
        }
    }

C#’ta Gösterici(Pointer) – Gösterici Bildirimi ve Gösterici Operatörleri – IV.PNG
Tabi burada dikkat edilmesi gereken bir diğer husus ise sizeof operatörünün kullanıldığı struct içerisinde referans türünden bir property, değişken vs. olmaması gerekmektedir. Aksi taktirde hata ile karşılaşılacaktır.

Evet arkadaşlar…
Zor ve zahmetli bir makalemizin daha sonuna gelmiş bulunmaktayız. Sanıyorum ki gösterici operatörleri üzerine tam teferruatlı bir içerik ortaya koymuş olduk. Sizler öğrendiklerinizi sindirme ve birazda dinlenme moduna geçerken bendeniz ise bir sonraki makalemizin içeriği olacak göstericiler arasında tür dönüşümleri üzerine çalışmaya başlayabilirim.

Görüşmek üzere…
İyi çalışmalar…

Bunlar da hoşunuza gidebilir...

1 Cevap

  1. 10 Mayıs 2017

    […] devam ediyoruz. Yazı dizimizi takip edebilmeniz için bir önceki yazmış olduğumuz C#’ta Gösterici(Pointer) – Gösterici Bildirimi ve Gösterici Operatörleri – IV başlıklı makaleyi referans alabilir ve ilgili makale aracılığıyla yazı dizimizin diğer […]

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

*