Nesne Yönelimli Programlama Dillerinde baş aktör olan nesneyi (object) gerçek dünya metaforları ile izah edebiliriz. Bu gayet anlamlı olabilir. Fakat daha iyi kavramak, işin iç yüzünü, daha derinlerde çözümlemekle ancak mümkün olabilir. Derin sorumuz, “Bir program çalışma zamanında (runtime) bellek yönetimini (memory management) nasıl gerçekleştirir.”
Nesne yönelimli dillerle ilk tanışmam esnasında “new” anahtar kelimesini nerelerde ve neden kulanıldığını anlamak oldukça uzun zamanımı almıştı. Neden new anahtar kelimesi kullanıyoruz?Yapıcı metodların (Constructor) geri dönüş tipi neden bulunmaz? Çalışma zamanında durduk yere aldığımız ve canımızı çok sıkan “NullReferenceException” başlıklı hatalar neden alırız? İşte bütün bu soruların cevaplarını yazdığımız kodlarda, verilerin saklandığı yer olan RAM’de arayacağız.
Aynı zamanda Değer Tipi (Value Type) , Referans Tipi (Reference Type) kavramları da burada tanışacaklarımız arasında olacak.
Programlama dilleri uygulama yürütme süreci esnasında verilerin adresleneceği RAM bellek bölgesini birkaç farklı bölgeye ayırarak kullanırlar. Uygulama çalışma zamanında (runtime) açılacak olan değişken ve nesneleri bu bölgelerde oluşturarak, sahip oldukları verileri adreslerindeki yerlerine yerleştirirler. Öncelikli olarak bu bellek bölgelerini kısaca tanıyalım;
RAM Bellek bölgeleri Nelerdir?
I. Stack Bölgesi
Genel anlamda kullanılan dahili RAM ifadesidir. Stack bögelerine mikroişlemcide(CPU)’de bulunan “Stack Pointer (SP)” vasıtası ile ile doğrudan erişilebilir. SP o anda çalışılan bellek bölgesinin adresini tutar. Tahsis işlemi için önceden ayrılacak bölgenin büyüklüğünü bilmesi gerekir. Buna göre arttırım veya azaltım yaparak veriyi uygun adreste bulur. Bilmesi gereken bu büyüklükler, .NET platformunun altyapısını oluşturan JIT derleyiciler tarafından sabit değerler ile tayin edilir. Örneğin int tipi 32 bitlik olup 4 byte büyüklüğündedir. Bir diğer örnek long tipi 64 bit yani 8 byte, boolen tipi ise 1 bit için 1 byte yer tutar. Program yüklendiğinde okunulan değişken tanımına dair kodlarınızı bu sabit değerler eşliğinde hesaplayarak SP’nin pozisyonlarını doğru konumlandırması için bellek tahsisatını yapar. Yapısal programlama dillerinde (C, Pascal vs…) edinilen tecrübeler, verilerin hepsini stack bölgesinde tutmanın esnekliği azalttığını göstermiştir.
II. Heap Bölgesi
Stack’de olduğu gibi Heap bölgesi RAM’de bulunan hafıza alanıdır. Bütün C# nesneleri Heap bölgesinde oluşurlar. Stack’den farkı heap bölgesinde tahsisatı yapılacak nesnenin derleyici tarafından bilinmesine gerek duymaz. Heap bölgesinin kullanılması büyük esneklik kazandırır. Heap bölgesinden yer tahsisatı için “new” anahtarı kullanılır. Çalışma zamanında dinamik
olarak yaratılır. Derleme zamanında yapılmazlar.
Dezavantajı Stack bölgesine göre daha fazla işlem ayağı oluşması neticesi performans düşüklüğüdür. Ancak Nesne Yönelimli Programlama’nın temel ayağıdır.
III. Register Bölgesi
Stack ve Heap tahsisat mekanizmalarına göre çok hızlıdır. Sebebi mikroişlemcinin ikincil bellek bölgesinde bulunmasıdır. Mikroişlemcinin kaydedici (register) adını verdiğimiz bu bellek bölgelesine doğrudan erişim hakkımız yoktur. Tamamen altyapıda çalışan JIT (Just in Time) derleyicilerinin kontrolündedir.
IV. Static Bölge
Bellekteki herhangi bir bölgeyi temsil eder. Static alanlarda saklanan veriler programın bütün çalışma sürecinde ayakta kalırlar. Anahtar “static“ sözcüğüdür.
V. Sabit Bölge
Sabit(constant) değerler genellikle program kodlarının içine gömülü şekildedir. Değerleri asla değişmezler. Sadece okuma amaçlıdır.
VI. RAM Olmayan Bölge
Bellek bölgesini temsil etmeyen disk alanlarıdır. Kalıcı olması istenen verilerin saklandığı bölgedir.
Özellikle Stack ve Heap mantıksal alanları dahili RAM hafızada değişken (variable) ve nesnelerimiz için çok daha önemli konumlardadır.
Bu iki farklı mantıksal alanın birbirlerinden ayrılma nedeni ise değer tipi değişkenlerin ve referans tipi nesnelerin bu bellek bölgelerini farklı şekilde kullanmalarıdır. Farklılıklarını anlamak için Değer Tipi (Value Type) ve Referans Tipi (Reference Type) kavramlarını örneklerle inceleyelim.
Değer Tipleri
1 2 3 4 5 6 7 8 9 10 |
class Program { static void Main(string[] args) { int a; a = 5; } } |
Yukarıdaki basit örneğimizde int tipinde “a” adında ki değişkenimiz, sonraki satırda 5 değerine sahip oluyor. Bu durum “a” değişkenini direkt olarak RAM’in Stack adı verilen bölgesinde derleyicilerin direktifi ile 4 byte büyüklüğünde bir bloğu adreslenmesini sağlar. İçeriğine uygun değeri ise bu adreslenen bellek bloğunda saklar. Değişkendeki değere erişim doğrudan adreslenen bölgeye başvurularak yapılır.
Program kodunun ilerleyen satırlarında x veya i değişkenine okuma yönlü başvuruda bulunduğunuzda Stack bellek bölgesindeki adresinden alınır ve kullanılır.
1 2 3 4 5 6 7 8 9 10 11 12 |
class Program { static void Main(string[] args) { int i=4; double x = 5; Console.WriteLine(i); Console.WriteLine(x); } } |
Eğer bu değişkenlere yazma yönlü bir durum meydana getirilirse sahip olacakları yeni değerler adreslenen yerlerine yazılır. Dolaysıyla önceki değerlerini kaybedeceklerdir.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Program { static void Main(string[] args) { int i=4; double x = 5; i = 8; x = 9; } } |
Diğer bir senaryomuzda ise değişkenlerin birbirlerine eşitlenmesi durumu diğer bir adıyla kopyalanmaları sürecini ele alalım. Bir değişkeni başka bir değişkene eşitlendiğinizde yalnızca sahip olduğu değer o an kopyalanır. Sonrasında değişkenlere atayacağınız yeni değerler yalnızca yine kendisini etkileyecektir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Program { static void Main(string[] args) { // Tanımlama int i=4; double x = 5; // Eşitleme x = i; // Sonra x = 6; i = 7; } } |
Görüldüğü üzere değişkenlerin değer tipi olarak nitelendirilmesinin nedeni tüm işlemlerin adreslenen RAM hücresine doğrudan erişim yapılması ve sahip oldukları değerlere direkt uygulanmasından kaynaklanmaktadır.
Aşağıda saydıklarımız değer tipi davranışında bulunan türlerdir ;
- Değişken türleri: “int”, “long”, “float”, “double”, “decimal”, “char”, “bool”, “byte”, “short”
- Yapılar : “struct”
- Sayılabilir Tipler : “enum”
Referans Tipleri
Referans Tipleri Stack ve Heap bellek alanlarını birlikte kullanılır. Söz konusu nesnenin sahip olduğu değerler Heap bellek alanında korunur. Bu ayrılmış bellek alanının başlangıç adres bilgisini (işaretçisi) ise Stack bellek alanında tutar. Nesneneye dair her türlü erişim işte bu işaretçi(pointer) üzerinden yapılır. Kısa bir örnekle inceleyelim;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class Program { static void Main(string[] args) { Urun u1; u1 = new Urun(); u1.numara = 1; u1.fiyat = 50; } } class Urun { public int numara { get; set; } public float fiyat { get; set; } } |
Urun tipi, class ifadesinde geçen özelliklere sahip bir sınıftır. “u1” nesnesi Urun tipinden tanımlanmıştır. “u1” değişkeni belleğin Stack bellek bölgesinde oluşur. Bu anda nesne “null” durumdadır. “new” anahtar kelimesi ile Heap bellek alanında nesnenin büyüklüğü kadar bir alan tahsistı yapılır. Bu ayrılmış alanın işaretçi adres bilgisi (referans) “u1” nesnesinde saklanır. Nesnemiz artık null durumda değil, Heap’ta saklanan değerlere ulaşılabilen refransa sahiptir.
Bir nesne null durumda iken ona erişim yapmaya (okuma-yazma) kalkarsanız, “NullReferenceException” hatasını alırsınız.
Referans tiplerini değer tiplerinden ayıran en önemli senaryo, Aynı türde iki nesne birbirlerine eşitlendiğinde görülür. Örneğin ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class Program { static void Main(string[] args) { Urun u1; u1 = new Urun(); u1.numara = 1; u1.fiyat = 50; Urun u2; u2 = new Urun(); u2.numara = 2; u2.fiyat = 100; u1=u2; } } |
Örneğimizde u1 ve u2 Urun tipinde farklı özelliklere sahip iki nesnedir. Ancak “u2=u1” şeklinde bir eşitleme ile aslında eşitlenen Stack tarafındaki nesnenin referansları olacaktır. Kısaca u1’in referansı u2’ye kopyalanacaktır. Bu durumda referansları eşitlenen nesneler dolaysıyla aynı Heap bellek alanındaki değerlere işaret olacaktır. Bu durumda her iki nesne birbirine gerçekten eşit hale gelecektir. Bu noktadan sonra u1 veya u2 nesnelerinin özelliklerinde yapılacak her türlü okuma yazma işlemlerinde her zaman birbirlerini etkileyeceklerdir.
- “string”, “object”, “class”, “interface”, “array”, “delegate”, “pointer” referans tipi davranışında bulunan türlerdir.
Çok teşekkür ederim. Çok güzel açıklamışsınız.
Gerçekten güzel bir makale olmuş. Emeğinize sağlık.
Bu gönderiye yorum yapılmaması ilk bakışta beni şaşırttı ama çok geçmeden anladım. Çok güzel bir paylaşım olmuş. Biz toplum olarak biraz aceleci olduğumuz için hemen “Merhaba Dünya” ile kodlamaya başlıyoruz. İşini mantığını da öğrenmek lazım. Paylaşımınız için teşekkürler.
yazilimbilisim.net ailesi olarak güzel yorumunuza biz teşekkür ediyoruz.
Bu yazdığınız konu hakkında kafama takılan bir kaç soru var:
1) Program içerisinde static olarak tanımladığımız değişkenler static bölgede mi tutuluyor?
2) Program içerisinde tanımladığımız (ör. const int a = 20) sabiti belleğin sabit bölgesinde mi tutuluyor?
her iki sorunuzun cevabı için doğru tespitler yapmışsınız. const türevi tipler sabit alanda, static anahtar kelimeli değişkenler static bölgede bulunmaktadır. Tabi ki bu bellek bölgelerinin tahsisatı tamamen işletim sisteminin sorumluluğunda gerçekleştirilir. Nesne yönelimli programlama paradigmasında öğrenilmiş tasarım kalıplarında static değişkenler özeĺlikleri bakımından oldukça işlevseldir. Örneğin “singleton design pattern” incelemenizi öneririm…
[…] hatırlayanımız kaç tane olur?) Hafıza ile ilgili daha fazla bilgi almak istiyorsanız. https://www.yazilimbilisim.net/c-sharp/c-deger-ve-referans-tipi-bellek-yonetimi/ yazısını […]
[…] […]