Merhaba sevgili e-bergi okurları. Yaz sıcağının kendini iyiden iyiye hissettirdiği bu ay, sizlere 'Hareketli Hafıza Edinimi' kavramından bahsetmek istiyorum.
Bildiğiniz gibi bilgisayardaki programlar, hafızada yaşamlarını sürdürürler. Her program için belli miktarlarda yerler ayrılmıştır; yani ne kadar çok programınız varsa, hafızada o kadar yer işgal ediyorsunuzdur. Hafızanızda fiziksel bir birim olduğundan, yani hafızanız sonsuz olmadığı için, elinizdeki hafızayı verimli kullanmanız, tabii ki sizin yararınıza olacaktır. Hareketli hafıza edinimi adı verilen yol ise bize bu verimi sağlar.
Nedir bu 'Hareketli Hafıza Edinimi'?
Özellikle programcılık dünyasında sık başvurulan bu yolu şu örnekle anlatabiliriz:
Toplam hafızanızın 2500 byte, bu hafızada yaşayan iki adet 1000'er byte'lık yer isteyen ve hareketli hafıza edinimi yapmayan programınızın olduğunu düşünün. Bu programlar başlarken işletim sisteminize “Bana 1000 byte yer ver!” talebinde bulunduktan ve 1000'er byte'larını aldıktan sonra kaynak kodları ne gerektiriyorsa o işi yapmaktadırlar; ama gelin görün ki, kendilerine ayrılmış olan hafızada sadece 100'er byte'lık bilgi içeriyorlar. 900'er byte'lık fazladan alan bu iki programa tahsis edilmiş durumda; ama programlar bu alanı kullanmıyorlar. Siz de , mesela 1500 byte hafıza gereksinimi olan başka bir program başlatmak istiyor fakat başlatamıyorsunuz; çünkü sizde 500 byte var. 2500 byte'lık toplam hafızanızın 1800 byte'ı gereksiz yere boşta durmakta ve siz bu alanı kullanamamaktasınız. Sıkıntı verici, değil mi?
Tanım olarak hareketli hafıza edinimi kavramı, programların peşin peşin değil de, sadece gerektiği zaman işletim sisteminden yer istemeleridir. Bunu da çalışmaya başlamadan önce değil çalışmaları sırasında yaparlar (zaten hafıza gerektiğini anlaması için çalışması gerek). Yani örnekteki programlarınız hareketli hafıza edinimi yapan programlar olsaydı, 1000'er byte değil, 100'er byte kaplayacaklardı. Siz de kurtardığınız 1800 byte'ınızı başka kötü emelleriniz için kullanabilecektiniz.
Güzelmiş. Peki Nasıl Yaparız Biz Bu Hareketli Hafıza Edinimini?
Daha yaygın olarak C/C++ 'da kullanıldığından ondan başlayayım.
C dilinde işletim sistemine “Bana heap'ten şu kadar yer ver!” demenin yolu “malloc” fonksiyonudur. (heap: hafızanın malloc ile yer aldığımız bölümü) Argüman olarak da ne kadar byte yer istediğimizi veriyoruz, malloc(25000) ifadesi “Bize 25000 byte'lık yer ver.” anlamına geliyor. Bizim elimize gelen 25000 byte değil burada; 25000 byte'ın ilk byte'ını gösteren bir işaretçi (pointer).
*bizimpointer = malloc (25000);
Dikkat etmemiz gereken bir nokta ise, bu işaretçinin bir 'tür'ü olmadığı. Buradaki işaretçinin gösterdiği kutunun içine integer mı floating point mi konacağını malloc bilmiyor. Onun tek görevi bize istediğimiz kadar yer sağlamak. İşaretçinin türünü belirlemek bizim sorumluluğumuz. Yani;
int * bizimpointer;*bizimpointer = malloc (25000);
Aynı işi yapan bir de “calloc” fonksiyonumuz var. Çağrılması sırasında “malloc”tan farkı 2 tane argüman alması. İkincisi bir türün büyüklüğü, ilki ise bu büyüklükten kaç tane istediğimiz. Mesela;
float * bizimpointer2;*bizimpointer2 = calloc(25, sizeof(float));
dediğimiz zaman, 25 tane floating point'in sığabileceği büyüklükte yer almış oluyoruz.
“calloc”un kullanılmasınını asıl sebebi ise, aldığımız hafızadaki bütün bit'leri sıfırlaması (initialize). “calloc” fonksiyonunun aksine, verdiği alanı sıfırlamadığından, “malloc” kullanarak aldığımız yerin içinde ne olduğunu bilmiyoruz. Fakat bu özelliği calloc'un hızını büyük ölçüde azaltıyor.
Hafızayı aldıktan sonra büyütüp küçültmemizi sağlayan “realloc” fonksiyonuna gelelim. Argüman olarak bir işaretçi ve düzenlemek istediğimiz hafızanın yeni boyutunu alıyor ve yine bir işaretçi dönüyor. Kullanımına örnek olarak:
int * bizimpointer3;bizimpointer3 = malloc(sizeof(int) *60);
Sonra baktık ki, 60 tane integer büyüklüğünde yer yetmeyecek bize,
*bizimpointer3 = realloc ( *bizimpointer3, sizeof(int) *90);
dediğimizde, bizimpointer3 işaretçisinin gösterdiği alanı 90 adet integer'ın sığabileceği büyüklüğe getirmiş oluyoruz.
“realloc” hafızayı küçültme işlemini sorunsuz yapabiliyor; fakat büyütme işlemini yaparken başarısız olursa, yani -buradaki örnekte- 60 integer'lık yerin sonunda başka bir 30 integer için yer yoksa, 60 integer'lık alandaki tüm veriyi, veri kaybı olmadan, başka bir yere taşıyıp sonuna bir 30 integer'lık yer daha ekliyor. Başka bir olasılık da, hiçbir yerde 30 integer'lık boşluk olmadığı zaman, asıl işaretçinin yerini değiştirmiyor ve döndüğü işaretçi bir NULL işaretçi (boşluğu gösteren işaretçi) oluyor.
Bu üç fonksiyonu kullanarak programımız için gerekli hafızayı aldık, kullandık, yönettik. İşimiz bittiğinde bu ödünç aldığımız hafızayı geri vermemiz gerek, öyle değil mi? Bunun için de “free” fonksiyonumuz var. Tek argüman olarak, edindiğimiz hafızanın başını gösteren işaretçiyi alıyor. Kodumuzdaki
free(bizimpointer986);
satırı malloc ile bir zamanlar almış olduğumuz ve o alanın başını gösteren işaretçiyi, malloc fonksiyonunun döndüğü değer, bizimpointer986'ya eşitlediğimiz alanı terk etmemizi sağlıyor.
Bu fonksiyon kodunuzda yer almazsa hafızadan sürekli yer alıp hiç geri vermemiş olursunuz (bu hataya özel olarak 'hafıza kaçağı' deniyor), bir süre sonra da -doğal olarak- alacak hafızanız kalmaz. Bu yüzden unutmayın, aman diyeyim!
C++ için “malloc, free” yerine “new,delete” fonksiyon çiftini kullanabilirsiniz. Özellikle objelerle çalışırken kolaylık sağlayan “new”, argüman olarak direk byte sayısını değil de, bir tür bilgisiyle beraber, bir ilklendirme(initialize) sayısı veya -array yaratılacaksa- array'in boyutunu alır ve yine bir işrateçi döner.
int * bizimpointer_int = new int(84);
satırı bizimpointer_int işaretçisinin gösterdiği yere 1 tamsayı boyutunda hafıza alır ve o hafızaya 84 sayısını koyar (initialize). Daha fazla yer almak istersek:
int * bizimpointer_int_daha_fazla_yer = new int[14];
satırıyla 14 tamsayılık hafıza yeri almış oluruz.“delete” fonksiyonunun ise “free”den bir farkı yoktur.
C# ve java'nın bir özelliği de “free”nin görevini yapan bir fonksiyon yerine, gömülü bir çöpçü (garbage collector) barındırmalarıdır. Siz programınızı bu ikisinden birinde yazarken, sadece aldığınız hafızaya yoğunlaşırsınız, geri vermekle uğraşmazsınız. Çöpçü, alınan hafıza bloklarının artık kullanılmadığını anladığı zaman otomatik olarak buraları işletim sizteminize iade eder. Tabii ki bu da çalışma sırasında programınızı yavaşlatan bir etkendir.
Tamam Böyle Yapılıyormuş. Peki İyi ve Kötü Yanları Neler?
Başta belirttiğimiz gibi, hareketli hafıza edinimi basitçe size yerden kazanç sağlar. Her zaman programlarımızın daha az yer kaplamasını isteriz, değil mi? Böylece diğer programlar da kendilerine rahatça yaşam alanı bulabilirler.
Hareketli hafıza ediniminin kötü yanı ise, biraz detaylarda gizli. Siz işletim siteminize “Bana şuradan şu kadar yer ver!”, “Bana buradan bu kadar yer ver!” dediğiniz zamanlarda işletim sisteminiz size bu yerleri sağlar; ancak bu yerler hafızada ardışık halde durmazlar (array'ler hariç, onlar tek parça halinde). Daha çok hafızanın belli bir kesimine parça parça dağılmış haldedirler:
Sizin her “Bana yer ver!” komutunuzla beraber programınız, bu delik deşik mayın tarlasında sizin istediğiniz boyutta bir boşluk aramaya başlar. Bulmasının hız açısından ayrı bir sorun olmasının yanı sıra, bulduğu zaman da işi bitmez. Mevcut ve 'sizin istediğiniz kadar'lıktan büyük boşluklar içinde en küçüğünü arar. Zaman açısından bu da hayli istenmeyen bir olay.
Başka bir sıkıntılı durum da, 'adres parçalanması' (adress fragmantation) adı verilen durum. İşletim sisteminden 10 kB'lık yer aldınız; ama işletim sistemi bu yeri size 20 MB'lık bir boşluğun tam ortasından verdi. Siz şimdi buradaki boşluk için 15 MB'lık bir hafıza talebinde bulunamıyorsunuz. İşletim sistemi bu isteğiniz için başka bir yer arıyor. Eğer hafızada bir çok yerde böyle 10'ar, 20'şer kB'lık edinim yaptıysanız, sizin 15MB'lık tek parça verinizi koyacak yeriniz kalmıyor.
E Peki Alternatifleri Var mı?
Var. Hareketlinin yanında bir de sabit hafıza edinme yolunuz var (static memory allocation). Bu program çalışırken değil, derlenirken işletim sisteminden hafıza talep etme oluyor. Bir C programınızda kullandığınız, mesela, int a; ifadesi program çalışmadan önce 1 integer'lık yer alıyor ve o yere 'a' ismini veriyor.
Başka bir alternatif de 'yığın tabanlı hafıza edinme' (stack based memory allocation). Bilgisayar biliminde 'yığın' tabiri, “son giren ilk çıkar” mantığına tekabül eder. Burada programınızın edineceği hafıza parçası belli olduğundan, hareketli hafıza edinimine göre çok daha hızlı çalışır. Bu mantığı fonksiyonlarınız kullanır. Her çağrıldıklarında bir yığın oluşturarak kendi değişkenlerini orada tutarlar. İşleri bittiğinde ise otomatik olarak yığını ortadan kaldırıp, çağırıldıkları yere dönerler. Yığının kendisini ortadan kaldırma özelliği sizin için rahatlık verici olur; çünkü kullanılan hafızayı geri vermek gibi bir derdiniz olmaz. Bu tip hafıza edinmelerini, geçici bilgiler söz konusuysa veya fonksiyonda elde edilen bilgi daha sonra lazım değilse kullanmanız uygun olur.
Sanırım artık hareketli hafıza edinimi gibi artistik bir ismi olan kavram hakkında yeterince bilgi sahibi oldunuz. Şimdi size düşen, yeni bir dosya açıp elinizi kodlarla kirletmek; böylece uygulama deneyimi kazanmak. Hadi bakayım, açtığınızı göreceğim. Hadi!
Kaynaklar
- http://en.wikipedia.org/wiki/Dynamic_memory_allocation
- http://en.allexperts.com/e/s/st/stack-based_memory_allocation.htm
- http://www.technoplaza.net/programming/lesson6.php
- http://en.wikipedia.org/wiki/Malloc#calloc
- http://www.cprogramming.com/tutorial/dynamic_memory_allocation.html
- http://en.wikipedia.org/wiki/New_%28C%2B%2B%29
- http://www.cprogramming.com/tutorial/virtual_memory_and_heaps.html