C dilindeki strtok, remove, rename ve system fonksiyonları.
C dilinde dinamik bellek yönetimi
1. Dinamik BellekYönetimi – Dynamic Memory Management
*** Yazının başından sonuna kadar bazı noktalarda fonksiyon(function) kelimesi yerine türkçe karşılığı
işlev kelimesi kullanılmıştır. ***
Bu yazımız da C dilinde dinamik bellek yönetimi konusunu işleyeceğiz. Dinamik bellek yönetimi C
dilinde çok önemli bir yer tutmaktadır. Hiç kuşkusuz bunun sebeplerinden birisi maliyet durumudur.
Yani sınırsız bir bellek alanımızın olmayışı bizi belleğimizi verimli kullanmaya itiyor. Aksi halde
yazılan programlar gerektiğinden fazla bellek tüketecek ve yavaş çalışacaktır.
Bellek yönetimine girmeden bellek yapısından, heap ve stack kavramlarından bahsetmek gerekir.
Stack ile başlayalım.
Stack, türkçe karşılığı yığın olan RAM bellekte bulunan soyut bir yapıdır. Belirli bir düzene ve
kurala göre verileri içerisinde saklar. Bu kural yapısı kısaca LIFO yani (Last in First Out) son giren
ilk çıkar mantığında çalışmaktadır.
Kısaca LIFO nun mantığını günlük hayattan örneklendirmek gerekirse şöyle açıklayabilirim.
Mutfakta kirli tabaklarınız var önce deterjanla köpürtüp temizleyip üst üste diziyorsunuz. Bu
şekilde 10-15 tabağı üst üste dizdiğinizi düşünün. Temizleme işleminin ardından durulama aşaması
geldiğinde ilk olarak, en alttaki tabaktan değil en üstteki tabaktan başlarsınız. İlk önce en üstte ki
tabağı alır durular bir kenara koyarsınız sonra sırasıyla diğerlerini yıkar durularsınız. LIFO
mantığıda bu şekildedir. Stack de veriler, son giren Ilk çıkar mantığı ile işleme tabi tutulurlar. Bir
düzen vardır. Biz bu yapıyı farkında olmadan günlük hayatta bir çok kez kullanıyoruz. Bizim
yerimize kullandığımız bilgisayar bunu otomatik olarak yapıyor.
İlk değer vermediğimiz bir pointer oluşturduğumuz da bu pointer bellekte stack alanında bulunur.
Örnek char * türünde bir pointer oluşturalım.
Char *ptr;
Heap ile devam edelim.
Heap yapısının en önemli özelliği, veriler stack de olduğu gibi bir düzen veya kurala(LIFO) göre
değil karışık bir şekilde tutulurlar. Bir diğer fark ise Stack üzerinde ki veri hemen silinebilirken
Heap de ki veriyi silmek için çöp toplayıcı (garbage collector) algoritması gerekir.
Dinamik bellek yönetiminde bellek üzerinden belirli bir boyutta bellek alanı ayırmak istediğimizde,
bu alan heap den alınır. Aynı şekilde bu alanı büyütmek veya küçültmek istediğimiz de, gene heap
üzerinde işlem yapılır.
Yukarıda oluşturduğumuz pointer’a bir dizinin adresi ile ilk değer verelim.
char a[20] = { 0 };
char *ptr = a;
Artık ptr, a dizisinin adresini tutuyor.
Neden adres operatörünü kullanmadığımızı merak ediyor olabilirsiniz. Dizi işlemlerinde derleyiciye
char *ptr = &a; demek ile char *ptr = a; demek arasında bir fark yoktur.
Derleyiciden derleyiciye fark olsa da, tanımladığınız pointer veya değişken türlerinin bellekte
kaplayacağı alan az çok bellidir. Yukarıda tanımadığımız pointer bellek üzerinde 8 byte yer
kaplamaktadır. Linux altında kullandığım GCC derleyicisinde, tanımlanan değişken veya
pointerlara ait tür bilgileri ve hafızada kapladıkları alanları şöyledir.
2. Int türü 4 byte
Int * türü 8 byte
Double türü 8 byte
Double * türü 8 byte
Char türü 1 byte
Char * türü 8 byte
Bu bilgileri elde etmek için sizeof() işlevi kullanılabilir.
#include <stdio.h>
int main(){
printf("sizeof(char *) = %dn", sizeof(char *));
printf("sizeof(int *) = %dn", sizeof(int *));
printf("sizeof(int) = %dn", sizeof(int));
printf("sizeof(char) = %dn", sizeof(char));
printf("sizeof(double) = %dn", sizeof(double));
printf("sizeof(double *) = %dn", sizeof(double *));
return 0;
}
Şimdi gelelim dinamik bellek yönetimi fonksiyonlarını tanımaya.
Standart olarak C de gelen dinamik bellek yönetimi ile ilgili fonksiyonların bildirimleri “stdlib.h”
başlık dosyası içerisinde yer alır. Bu fonksiyonlar şunlardır:
malloc();
calloc();
realloc();
free();
Tanımladığımız diziler genelde belli boyutlara sahip oldukları için bellek üzerinde kapladıkları alanı
tahmin etmek kolaydır. Fakat öyle senaryolar var ki, tanımladığımız dizilerin boyutunu kestirmek
zor oluyor. Bu gibi durumlar da dizilerin boyutu dinamik olarak küçülüp büyüyebiliyor. Örnek,
yemekhaneye giriş çıkışların kontrol edildiği bir yazılımınız var. Yemekhaneye giriş çıkış yapan
kişilerin isim, soyisim, yaş, departman ve giriş saati gibi bilgilerini diziler üzerinde tutuyorsunuz.
Fakat o gün yemekhaneye kaç kişinin giriş yapacağını kestiremiyorsunuz. Giriş yapanların sayısı 10
da olabilir 100 de.. Siz de bu duruma uygun bir biçimde dizi tanımlamalısınız. Bu duruma uygun bir
biçimde dizi tanımlamak biraz zor ama dinamik bellek yönetimi ile kolay..
Şimdi fonksiyonlarımızın bildirimlerini ve tek tek nasıl kullanıldıklarını görelim.
Malloc işlevi
void *malloc(size_t size);
Gördüğünüz gibi malloc işlevi adres döndüren bir işlev.. Bizden size_t türünden bir boyut bilgisi
istiyor. (size_t türü unsigned int’tir)
Bize ne kadar bellek alanı istediğimizi soruyor ve bize bu bellek alanını kullanabilmemiz için bir
adres bilgisi dönüyor. Adres bilgisini saklamak için pointerları kullanıyoruz.
3. Şimdi bellekten 40 byte’lık bir alan tahsis edelim.
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *ptr = malloc(sizeof(int) * 10);
if(ptr){
printf(“Alan tahsis edildi”);
}
else{
printf(“Yetersiz bellek.n”);
}
return 0;
}
Calloc işlevi
Malloc işlevi ile 40 bytelık bir alan tahsisi gerçekleştirdik. Fakat bilmemiz gereken bir konu daha
var. Malloc işlevi ile tahsis edilen bellekte ki alanlar çöp değerler ile tahsis edilir.
Calloc işlevi ise tıpkı malloc işlevi gibi alan tahsis eder ve malloc işlevinden farklı olarak tahsis
ettiği bellek alanını sıfırlar. Yani bellek adreslerinden veri okunduğunda değer olarak 0 bulunur.
Calloc işlevinin bildirimi (function declaration) şu şekilde:
void *calloc(size_t ntane, size_t nboyutunda);
Calloc işlevinin parametre olarak bizden istediği iki adet argüman var. Bunlardan ilki kaç byte alan
istenildiğini belirten argüman, bir diğeri ise hangi tür boyutta istenildiğini belirten argüman..
Aşağıdaki kod bloğu daha açıklayıcı olur sanırım.
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *cptr = calloc(10, sizeof(int)); // sizeof (int) değer olarak 4 byte döner.. 4 * 10 = 40 byte
for(int i=0; i<10; ++i){
printf(“cptr[%d] = %dn”, i, cptr[i]);
}
return 0;
}
Hatırlarsak, malloc işlevinden farklı olarak calloc işlevi bellekteki değerleri sıfırlıyordu. Derleyip
çalıştırdığımız da bize ayrılan bellek alanında ki değerleri görebiliriz.
4. Realloc işlevi
Yazının ilk başında verdiğim yemekhane örneğine bir gönderme ile başlayalım. Hatırlarsak
yemekhane yazılımımız da bir dizi ile yemekhaneye giriş çıkış yapan kişilerin bilgilerini
tutuyorduk. Fakat kaç kişinin giriş yapacağı konusunda bir fikrimiz olmadığından, dizi ile
ayırdığımız bellek bloğu bize yetersiz gelebilir. Bu durumda ayırdığımız bellek bloğunu büyütüp
küçültme ihtiyacı duyarız. Bu işi yapan işlev realloc işlevidir.
Realloc işlevinin bildirimi şu şekildedir:
void *realloc(void *vp; size_t new_size);
Realloc işlevinin bildiriminden anlayacağımız üzere, işlev bizden iki argüman istemektedir. Birinci
argüman büyültme veya küçültme yapacağımız bellek bloğunun adresi, ikinci argüman ise adresi
alınan bellek bloğunun yeni boyutu.. İşlevi şu şekilde kullanıyoruz:
#include <stdio.h>
#include <stdlib.h>
int main()
{
size_t n;
printf("kac tamsayi: ");
scanf("%zu", &n);
int *pd = (int *)malloc(n * sizeof(int));
if (!pd) {
printf("bellek yetersizn");
}
size_t nplus;
printf("kac tamsayi daha ilave edilsin: ");
scanf("%zu", &nplus);
pd = realloc(pd, (n + nplus) * sizeof(int));
if (!pd) {
printf("bellek yetersizn");
return 1;
}
}
Kodu yorumlamaya başlarsak :
size_t türünde n adında bir değişken tanımlıyoruz. Scanf ile tanımladığımız değişkene bir tamsayi
değeri atıyoruz. Daha sonra pd adında, geri dönüş türü int * olan bir pointer tanımlıyoruz ve buna
değer olarak int * türüne cast edilmiş(dönüştürülmüş) malloc işlevi ile n * sizeof(int) kadar bellek
alanı tahsis ediyoruz. İf deyimi ile bellek alanının tahsisi başarılı mı değil mi kontrol ediyoruz.
Artık pd adında bir pointer da n adet, int türünden bellek alanımız var. Bu bellek alanını büyütmek
için tekrar size_t türünden nplus adında bir değişken tanımlıyoruz. Printf işlevi ile var olan bellek
alanına ne kadar daha ekleme yapılması istenildiğini soruyor ve bu değeri scanf işlevi ile nplus
değişkenine değer olarak veriyoruz.
5. Daha sonra realloc işlevini çağırıp işlem yapılacak bellek adresinin tutulduğu pointer’ı veriyoruz.
İkinci argüman olarak da var olan n sayısına nplus ı ekleyip sizeof(int) geri dönüş değeri ile
çarpıyoruz. Sizeof(int) geri dönüş değerini 4 byte döneceğinden n + nplus toplamı 4 ile çarpılacak
ve çıkan değer kadar “byte” bellekten tahsis edilmiş olacak. Bu örnek bellek alanının
büyütülmesine verilen örnektir.
Free işlevi
free işlevi, malloc veya calloc ile tahsis ettiğimiz bellek alanlarını işimiz bittiğinde tekrar serbest
bırakmamıza yarayan çok güzel bir işlevdir. Dinamik bellek yönetiminde malloc veya calloc ile
ayrılan bellek, heap bellek alanından verilir demiştik. Heap alanından aldığımız bellek bloğunu free
ile geri vermezsek bellek alanı dolduğunda ve program işini yapamayacak duruma geldiğinde
“memory leak” dediğimiz bellek sızıntısına sebep olur.
***Aşağıda madde madde belirtilen davranışlarda kesinlikle bulunmayın!***
1-) Dinamik bellek işleviyle yeri elde edilmemiş bir bellek bloğunu asla free etme girişiminde
bulunmayın.
2-) free işleviyle bir dinamik bellek bloğunu küçültme girişiminde bulunmayın. Free ile bloğun
tamamını geri verebilirsiniz.
3-) free edilmiş bir bellek bloğunun adresini tekrar free işlevine döndürmek tanımsız davranıştır.
(undefined behavior)
4-) Geri verilmiş bir bellek bloğunu kullanma girişiminde bulunmayın. Tanımsız
davranıştır.(undefined behavior)
5-) Tahsis ettiğiniz dinamik bellek bloğunu free ile geri vermeyi unutmayın.
Free işlevinin bildirimi şöyledir.
Void free(void *ptr);
Gördüğünüz üzere free bizden yanlızca free edilecek yani geri verilecek bellek bloğunun adresini
istiyor. Yukarıda malloc ve calloc işlevleri ile tahsis ettiğimiz bellek bloklarını şu şekilde geri
verebiliriz.
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *cptr = calloc(10, sizeof(int)); // sizeof (int) değer olarak 4 byte döner.. 4 * 10 = 40 byte
for(int i=0; i<10; ++i){
printf(“cptr[%d] = %dn”, i, cptr[i]);
}
free(cptr); // geri verilecek bellek bloğunun adresi
return 0;
}
Yazımızı okuma zahmetinde bulunduğunuz için teşekkürler.