13 Kasım 2020 Cuma

Malloc Calloc Realloc fonksiyonlari

 Malloc fonksiyonu

malloc fonksiyonu programın çalışma zamanı sırasında bellekten dinamik tahsisat yapmak için
kullanılır. Fonksiyonun prototipi aşağıdaki gibidir :
void *malloc(size_t nbyte);
size_t türünün derleyiciye yazanların seçimine bağlı olarak unsigned int ya da unsigned long
türlerinden birinin yeni tür ismi olarak tanımlanması gerektiğini, ve sistemlerin hemen hemen
hepsinde size_t türünün unsigned int türü olduğunu hatırlayalım.
Fonksiyona gönderilecek arguman tahsis edilmek istenen bloğun byte olarak uzunluğudur.
Tahsis edilen alanın sürekli (contigous) olması garanti altına alınmıştır. malloc fonksiyonunun
geri dönüş değeri tahsis edilen bellek bloğunun başlangıç adresidir. Bu adres void türden
olduğu için, herhangi bir türden göstericiye sorunsuz bir şekilde atanabilir. Bu adres herhangi
bir türden göstericiye atandığı zaman artık tahsis edilen blok gösterici yardımıyla bir dizi gibi
kullanılabilir. malloc fonksiyonunun istenilen bloğu tahsis etmesi garanti altında değildir. Birçok
nedenden dolayı malloc fonksiyonu başarısız olabilir. Bellekte tahsis edilmek istenen alan kadar
boş bellek bulunmaması sık görülen başarısızlık nedenidir. malloc fonksiyonu başarısız
olduğunda NULL adresine geri döner. malloc fonksiyonu bellekten yer ayırdıktan sonra
işleminin başarılı olup olmadığı mutlaka kontrol edilmelidir. malloc fonksiyonu ile bellekte bir
blok tahsis edilmesine ilişkin aşağıda bir örnek verilmiştir.
char *ptr;
ptr = (char *) malloc(30); /* bellekte 30 byte'lık bir yer tahsis edilmek isteniyor */
if (ptr == NULL) {
printf("cannot allocate memory\n");
exit(EXIT_FAILURE);
} /* başarısızlık durumunda program sonlandırılıyor */
printf("please enter the name : ");
gets(ptr); /* tahsis edilen alana klavyeden giriş yapılıyor */
...
Şüphesiz, malloc fonksiyonu başarısız olduğunda programı sonlandırmak zorunlu değiliz.
Atama ve kontrol bir defada da yapılabilir.
if ((ptr = (char *) malloc(30)) == NULL) {
printf("cannot allocate memory\n");
exit(EXIT_FAILURE); /* başarısızlık durumunda program sonlandırılıyor */
}
malloc fonksiyonu ile yapılan dinamik tahsisatın başarılı olup olmadığı mutlaka kontrol
edilmelidir. Fonksiyonun başarısız olması durumunda, geri dönüş değerinin aktarıldığı gösterici
aracılığı ile bellege birşey yazılmaya çalışılırsa, atama NULL adrese yapılmış olur. (NULL pointer
assignment). Programın yanlış çalışması kaçınılmazdır.
Programcılar çoğu kez, küçük miktarlarda ve çok sayıda bloğun tahsis edilmesi durumunda,
kontrolü gereksiz bulma eğilimindedir. Oysa kontrolden vazgeçmek yerine daha kolaylaştırıcı
yöntemler denenmelidir. Örneğin p1, p2, p3, p4, p5 gibi 5 ayrı gösterici için 10'ar byte alan
tahsis edilmek istensin. Bu durumda kontrol mantıksal operatörler ile tek hamlede yapılabilir.

char *p1, *p2, *p3, *p4, *p5;
...
p1 = (char *)malloc(10);
p2 = (char *)malloc(10);
p3 = (char *)malloc(10);
p4 = (char *)malloc(10);
p5 = (char *)malloc(10);
if (!(p1 && p2 && p3 && p4 && p5)) {
printf("cannot allocate memory!..\n");
exit(EXIT_FAILURE);
}
malloc fonksiyonu blokların herhangi birinde başarısız olursa bu durum if deyimi içinde tespit
edilmektedir.
malloc fonksiyonunun geri dönüş değeri void türden bir adres olduğu için, bu adres
sorunsuzca her türden göstericiye atanabilir. Ancak okunabilirlik açısından malloc
fonksiyonunun geri dönüş değeri olan adresin, tür dönüştürme operatörü yardımıyla,
kullanılacak göstericinin türüne dönüştürülmesi tavsiye edilir. Böylece malloc fonksiyonu ile tahsis edilen bloğun ne türden bir diziymiş gibi kullanılacağı bilgisi de açıkça okuyucuya
verilmiş olabilir.
C dilinin standartlaştırılmasından önceki dönemde void türden göstericiler olmadığı için, malloc
fonksiyonunun geri dönüş değeri char türden bir adresti ve bu durumda, geri dönüş değeri
olan adresin, char türü dışında bir göstericiye atanması durumunda tür dönüşümü bir
zorunluluktu.
int türden bir dizi için dinamik olarak tahsisatı yaptığımız düşünelim. Örneğin bir kullanıcının
klavyeden int türden sayılar gireceğini düşünelim. Kullanıcıdan, kaç tane sayı gireceği bilgisi
alınsın ve istenilen miktardaki sayıyı saklayabilecek bir alan dinamik olarak tahsis edilsin.

int main()
{
int number;
int i;
int *ptr;
printf("kaç tane sayı girmek istiyorsunuz? ");
scanf("%d", &number);
ptr = (int *) malloc(number * 2);
...
return 0;
}

Yukarıdaki kod parçasında int türü uzunluğunun 2 byte olduğu varsayılmıştır. Kaynak kod int
türü uzunluğunun 4 byte olduğu bir sistemde (örneğin UNIX) derlenirse problemler ortaya
çıkacaktır. Bu taşınabilirlik problemi sizeof operatörünün kullanılmasıyla çözülür.
ptr = (int *) malloc(number * sizeof(int));
Artık çalışılan sistem ne olursa olsun number sayıda tamsayı eleman için alan tahsis edilmiş
olacaktır.
malloc fonksiyonu birden fazla çağırılarak birden fazla alan tahsis edilebilir. malloc fonksiyonun
farklı çağırımlarıyla tahsis edilen blokların bellekte ardışıl olması garanti altına alınmış değildir.
Bu yüzden fonksiyonun geri dönüş değeri olan adres mutlaka bir göstericide saklanmalıdır.
Aşağıdaki kod parçası ardışık olarak çağırılan malloc fonksiyonlarının ardışık bellek blokları
tahsis edeceğini varsaydığı için yanlıştır.
#include <stdio.h>
#include <stdlib.h>
int main()
{
int * ptr;
int i;
ptr = malloc(sizeof(int) * 10);
malloc(sizeof(int) * 10); /* tahsis edilen alan daha önce tahsis edilen alanın hemen
altında olmak zorunda değildir */
for (i = 0; i < 20; ++i)
ptr[i] = 5;
return 0;
}
malloc fonksiyonu ile tahsis edilen bloğun içinde rasgele değerler vardır. Aşağıdaki kod parçası
ile bunu test edebiliriz.
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *ptr;
int i;
ptr = (int *) malloc(10 * sizeof(int));
if (ptr == NULL) {
printf("yetersiz bellek!..\n");
exit(1);
}
for (i = 0; i < 10; ++i)
printf("ptr[%d] = %d\n", i, ptr[i]);
return 0;
}
Dinamik bellek fonksiyonları kullanılarak tahsis edilebilecek bellek bölgesi heap olarak
isimlendirilmiştir. heap alanı donanımsal bir kavram olmayıp sistemden sisteme
değişebilmektedir. (C++ dilinde bu alan free store olarak isimlendirilmektedir.)
malloc fonksiyonunun parametre değişkeni unsigned int (size_t) türünden olduğuna göre,
DOS altında en fazla 65535 byte (64K) ardışıl tahsisat yapılabilir. Oysa UNIX, WINDOWS ve
diğer 32 bitlik sistemlerde unsigned int türü 4 byte uzunluğund olduğuna göre, bu
sistemlerde teorik olarak, malloc fonksiyonu ile 4294967295 byte (4 MB) uzunluğunda ardışıl
bir tahsisat yapılabilir.Tabi bu durum, tahsisatın yapılabileceği anlamına gelmez.

calloc fonksiyonu

calloc fonksiyonu malloc fonksiyonuna çok benzer. Prototipi aşağıdaki gibidir.
void *calloc(size_t nblock, size_t block_size);
calloc fonksiyonu kedisine gönderilen birinci arguman ile ikinci arguman çarpımı kadar ardışıl
byte'ı heap alanından tahsis etmek için kullanılır.
Elemanları int türden olan 100 elemanlı bir dizi için dinamik bellek tahsisatı calloc fonksiyonu
ile aşağıdaki şekilde yapılabilir:
int *ptr;
...
ptr = (int*) calloc(100, sizeof(int));
if (ptr == NULL) {
printf("cannot allocate memory\n");
exit(EXIT_FAILURE);
}
...
Şüphesiz yukarıdaki kod parçasında calloc fonksiyonu şu şekilde de çağırılabilirdi:
ptr = (int*) calloc( sizeof(int), 100);
calloc fonksiyonunun malloc fonksiyonundan başka bir farkı da tahsis ettiği bellek bloğunu
sıfırlanmasıdır. calloc fonksiyonu tarafından tahsis edilen bloğunun tüm byte'larında sıfır
değerleri vardır. malloc fonksiyonu calloc fonksiyonuna göre çok daha sık kullanılır. Tahsis
edilen blok sıfırlanacaksa malloc fonksiyonu yerine calloc fonksiyonu tercih edilmelidir.
Örneğin 50 elemanlı int türden bir dizi için bellekten bir bloğu dinamik olarak tahsis ettikten
sonra sıfırlamak istediğimizi düşünelim. Bu işlemi malloc fonksiyonu ile yaparsak, diziyi ayrıca
döngü içerisinde sıfırlamamız gerekecektir :
int *ptr;
int i;
...
ptr = (int *) malloc(sizeof(int) * 100);
if (ptr == NULL) {
printf("cannot allocate memory!..\n");
exit(EXIT_FAILURE);
}
for (i = 0; i < 100; ++i)
ptr[i] = 0;
...
Oysa calloc fonksiyonu zaten sıfırlama işlemini kendi içerisinde yapmaktadır.
calloc fonksiyonu, bellek tahsisatını yapabilmek için, kendi içinde malloc fonksiyonunu çağırır.

realloc fonksiyonu

realloc fonksiyonu daha önce malloc ya da calloc fonksiyonlarıyla tahsis edilmiş bellek bloğunu
büyültmek ya da küçültmek amacıyla kullanılır. Prototipi diğer dinamik bellek fonksiyonlarında
olduğu gibi stdlib.h başlık dosyası içindedir.
void *realloc(void *block, unsigned newsize);
realloc fonksiyonuna gönderilen birinci arguman, daha önce tahsis edilen bellek bloğunun
başlangıç adresi, ikinci arguman ise bloğun toplam yeni uzunluğudur.
realloc fonksiyonu daha önce tahsis edilmiş bloğun hemen altında sürekliliği bozmayacak
şekilde tahsisat yapamaya çalışır. Eğer daha önce tahsis edilmiş bloğun aşağısında istenilen
uzunlukta sürekli yer bulamazsa realloc, bu sefer bloğun tamamı için bellekta başka boş yer
araştırır. Eğer istenilen toplam uzunlukta ardışıl (contigious) bir blok bulunursa burayı tahsis
eder, eski bellek bloğundaki değerleri yeni yere taşır ve eski bellek bloğunu işletim sistemine
iade eder. İstenen uzunlukta sürekli bir alan bulunamazsa NULL adresiyle geri döner.
Aşağıdaki programda yapılan dinamik bellek tahsisatı işlemlerini inceleyelim:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *ptr;
ptr = (int *) malloc(sizeof (int) * 5); /* 1 */
if (ptr == NULL) {
printf("bellek yetersiz!..\n");
exit(EXIT_FAILURE);
}
for (i = 0; i < 5; ++i) /* 2 */
ptr[i] = i;
ptr = realloc(ptr, sizeof(int) * 10); /* 3 */
if (ptr == NULL) {
printf("bellek yetersiz!..\n");
exit(EXIT_FAILURE);
}
return 0;
}
realloc fonksiyonunun başarılı olması tahsis edilen bloğun altında ilave tahsisat yapıldığı
anlamına gelmez. (realloc fonksiyonu daha önce tahsis edilmiş alanı başka bir yere taşıyarak
yeni bloğun başlangıç adresiyle geri dönmüş olabilir. Bu durumda eski blok serbest bırakılır ve
artık bu adresin bir güvenilirliği yoktur.
Yukarıdaki örnekte realloc fonksiyonunun tahsisatı daha önce tahsis edilen bloğu büyüterek
yaptığı varsayılmıştır. Eğer realloc fonksiyonu bir taşıma yapmışsa, yukarıdaki kodda bir
gösterici hatası yapılmış olacaktır, çünkü artık p göstericisinin gösterdiği adres güvenilir bir
adres değildir.
Bu yüzden uygulamalarda genellikle realloc fonksiyonunun geri dönüş değeri, realloc
fonksiyonuna gönderilen 1. adresi içinde tutan göstericiye atanır.
ptr = realloc(ptr, 100);
gibi. Ancak bu bir zorunluluk değildir. Eğer realloc fonksiyonu ile yapılan tahsisatın başarılı
olmaması durumunda program sonlandırılmayacaksa (örneğin artık daha fazla tahsisat
yapılamaması durumunda) daha önce dinamik olarak tahsis edilen bölgedeki değerler bir
dosyaya yazılacak ise, artık realloc fonksiyonunun başarısız olması durumunda, ptr
göstericisine NULL adresi atanacağı için ptr göstericisinin daha önce tahsis edilen blok ile ilişkisi
kesilmiş olacaktır.
Böyle bir durumda geçici bir gösterici kullanmak uygun olacaktır:
temp = realloc(ptr, 100);
if (temp == NULL)
printf("cannot allocate memory!..\n);
Bu durumda ptr göstericisi halen daha önce tahsis edilen bloğun başlangıç adresini
göstermektedir.
C standartları realloc fonksiyonu ile ilgili olarak aşağıdaki kuralları getirmiştir:
Bir bellek bloğunu genşletmesi durumunda, realloc fonksiyonu bloğa ilave edilen kısma
herhangi bir şekilde değer vermez. Yani eski bloğa eklenen yeni blok içinde rasgele değerler
(garbage values) bulunacaktır.
realloc fonksiyonu eğer daha önce tahsis edilmiş belelk bloğunu büyütemez ise NULL adresi ile
geri dönecektir ancak tahsis edilmiş olan (büyütülemeyen) bellek bloğundaki değerler
korunacaktır.
Eğer realloc fonksiyonuna gönderilen birinci arguman NULL adresi olursa, realloc fonksiyonu
tamamen malloc fonksiyonu gibi davranır. Yani:
realloc(NULL, 100);
ile
malloc(100);
tamamen aynı anlamdadır.
(Buna neden gerek görülmüştür? Yani neden malloc(100) gibi bir çağırım yapmak yerine
realloc(NULL, 100) şeklinde bir çağırımı tercih edelim?
Aşağıdaki programı inceleyelim :
#include <stdio.h>
#include <conio.h>
#include <ctype.h>
#include <stdlib.h>
void display_array(int *ptr, int size);
int find_max(int *ptr, int size);
int find_min(int *ptr, int size);
double find_ave(int *ptr, int size);
double find_stddev(int *ptr, int size);
int main()
{
int *ptr = NULL;
int ch, grade;
int counter = 0;
clrscr();
for (;;) {
printf("not girmek istiyor msunuz? [e] [h]\n");
while ((ch = toupper(getch())) != 'E' && ch != 'H')
;
if (ch == 'H')
break;
printf("notu giriniz : ");
scanf("%d", &grade);
counter++;
ptr = (int *) realloc(ptr, sizeof(int) * counter);
if (ptr == NULL) {
printf("cannot allocate memory!..\n");
exit(EXIT_FAILURE);
}
ptr[counter - 1] = grade;
}
if (counter == 0) {
printf("hiçbir not girişi yapmadınız!..\n");
return 0;
}
printf("toplam %d tane not girdiniz\n", counter);
printf("girdi§iniz notlar aşağıda listelenmektedir :\n");
display_array(ptr, counter);
printf("\nen buyuk not : %d\n", find_max(ptr, counter));
printf("en küçük not : %d\n", find_min(ptr, counter));
printf("notların ortalaması : %lf\n", find_ave(ptr, counter));
printf("notların standart sapması . %lf\n", find_stddev(ptr, counter));
free(ptr);
return 0;
}
Yukarıdaki programda:
ptr = (int *) realloc(ptr, sizeof(int) * counter);
deyiminde, döngünün ilk turunda ptr göstericisinin değeri NULL olduğu için realloc fonksiyonu
malloc gibi davranacak ve int türden 1 adet nesnelik yer tahsis edecektir. Ancak döngünün
daha sonraki turlarında realloc fonksiyonuna gönderilen adres NULL adresi olmayacağından,
daha önce tahsis edilen blok döngü içinde sürekli olarak büyütülmüş olacaktır.
Tahsis edilen bloğun serbest bırakılması
malloc ya da diğer dinamik bellek fonksiyonları "heap" diye isimlendirilen bir bellek bölgesinden
tahsisat yaparlar. Ancak heap alanı da sınırlıdır ve sürekli bu fonksiyonların çağırılması
durumunda, belirli bir noktadan sonra fonksiyonlar başarısız olarak NULL adresine geri
dönecektir. heap alanının büyüklüğünü aşağıdaki kod ile test edebiliriz :
#include <stdio.h>
#include <stdlib.h>
#define BLOCK_SIZE 512
int main()
{
long total = 0;
char *ptr;
for (;;) {
ptr = (char *) malloc(BLOCK_SIZE);
if (ptr == NULL)
break;
total += BLOCK_SIZE;
}
printf("toplam heap alanı = %ld byte\n", total);
return 0;
}
Dinamik bellek fonksiyonlarıyla tahsis edilen bir blok free fonksiyonu kullanılarak sisteme iade
edilebilir. free fonksiyonunun prototipi de diğer dinamik bellek fonksiyonlarınınkiler gibi stdlib.h
başlık dosyası içindedir:
void free(void *ptr);
free fonksiyonuna gönderilecek olan arguman, daha önce malloc, calloc ya da realloc
fonksiyonlarıyla tahsis edilmiş olan bellek bloğunun başlangıç adresidir. Bu blok heap alanına
iade edilmiş olur. Böylece malloc, calloc ya da realloc fonksiyonunun bundan sonraki bir
çağırımında iade edilen blok, tekrar tahsis edilme potansiyelindedir.
free fonksiyonuna arguman olarak daha önce tahsis edilen bir bellek bloğunun başlangıç adresi
yerine başka bir adres gönderilmesi şüpheli kod oluşturur (undefined behaviour)
yapılmamalıdır.
char *p1;
char s[100];
ptr = (char *)malloc(20);
....
free(ptr) /* Legal */
free(p1) /* Bu adreste dinamik bellek fonksiyonları ile tahsis edilmiş bir alan yok. Hata. */
free(s) /* Hata s dizisi için tahsisat dinamik bellek fonksiyonları ile yapılmamıştır. */
free fonksiyonu ile daha önce tahsis edilen blok sisteme iade edilir, ama bu bloğun başlangıç
adresini tutan gösterici herhangi bir şekilde değiştirilmez. Bloğun başlangıç adresini tutan, ve
free fonksiyonuna arguman olarak gönderilen gösterici, free fonksiyonun çağırılmasından sonra
artık güvenli olmayan bir adresi göstermektedir.
char *ptr = malloc(100);
...
free(ptr);
...
strcpy(ptr, "sifir zero"); /* yanlış, bir gösterici hatası!! */
Eğer realloc fonksiyonuna gönderilen ikinci arguman 0 olursa, realloc fonksiyonu tamamen free
fonksiyonu gibi davranır.
realloc(ptr, 0);
ile
free(ptr);
tamamen aynı anlamdadır.
(Buna neden gerk görülmüştür? Yani neden free(ptr) gibi bir çağırım yapmak yerine
realloc(ptr, 0) şeklinde bir çağırımı tercih edelim?

Hiç yorum yok:

Yorum Gönder

Her yorum bilgidir. Araştırmaya devam...