malloc tek bir soruyu yanıtlar: bana şu kadar bayt ver. <stdlib.h> içindeki iki yol arkadaşı sonraki soruları yanıtlar - bana şu kadar bayt ver, temizlenmiş (calloc) ve elimdeki bloğun daha büyük olması lazım (realloc).
calloc: Sayı, Boyut ve Sıfırlar
void *calloc(size_t count, size_t size);
Bir yerine iki argüman ve sonuç sıfır baytlarla dolu.
Klasik durum budur: her yuvanın sıfırdan başlaması gereken bir histogram ya da sayaç dizisi. malloc ile bir döngüye ihtiyacınız olurdu; calloc bunu tahsisin parçası olarak yapar ve çoğu zaman bedavaya, çünkü işletim sistemi zaten taze sayfaları önceden sıfırlanmış olarak dağıtır.
Taşma argümanı
İki argümana bölünme kozmetik değildir. Bir dosyadan okunan bir sayıyı düşünün:
size_t n = girdiden_gelen_devasa_deger;
int *a = malloc(n * sizeof *a); /* çarpım sarabilir */
int *b = calloc(n, sizeof *b); /* taşmayı saptamakla yükümlüdür */
n * sizeof *a, size_t'nin tutabileceğini aşarsa küçük bir sayıya sarar, malloc minik bir blokla başarılı olur ve sonraki her yazma sonun çok ötesine geçer. calloc'un standarda göre bunun yerine başarısız olup NULL döndürmesi gerekir. Bir boyut programınızın dışından geldiğinde o denetime sahip olmaya değer.
"Sıfırlanmış" aslında ne demek
calloc sıfır baytlar yazar. Tam sayılar ve karakterler için bu, istediğiniz şey olan sıfır değeridir. Göstericiler ve kayan noktalı değerler için tüm bitlerin sıfır olması yaygın her platformda boş gösterici ve 0.0 anlamına gelir, ama C standardı bunu vaat etmez. Kesinlikle taşınabilir olması gereken kod bunları açıkça atar.
calloc ile malloc + memset karşılaştırması
int *a = calloc(n, sizeof *a);
int *b = malloc(n * sizeof *b);
if (b != NULL) {
memset(b, 0, n * sizeof *b);
}
Aynı sonucu üretirler. calloc'u tercih edin: tek satırdır, taşma denetimini yapar ve büyük bloklar için belleğe hiç dokunmaktan kaçınabilir. Zaten her baytın üzerine yazmak üzereyseniz düz malloc'u tercih edin - hemen dolduracağınız bir megabaytı sıfırlamak saf israftır.
realloc: Boyutu Değiştirmek
void *realloc(void *p, size_t newsize);
realloc, içeriği iki boyuttan küçük olanına kadar eski blokla aynı olan newsize baytlık bir blok döndürür. Bloğu bulunduğu yerde genişletebilir ya da yeni bir tane tahsis edip kopyalayarak eskisini serbest bırakabilir. Hangisini yaptığını anlayamazsınız, dolayısıyla sonrasında kullanabileceğiniz tek gösterici onun döndürdüğüdür.
tmp gösterici deyimi
tmp etrafındaki üç satır, bu fonksiyonun bir üne sahip olmasının tüm sebebidir. Cazip kısayol bozuktur:
/* HATA: realloc başarısız olduğunda özgün bloğu sızdırır */
p = realloc(p, newsize);
if (p == NULL) {
return;
}
realloc başarısız olduğunda NULL döndürür ve özgün bloğu tahsis edilmiş ve değişmemiş bırakır. Sonucu doğrudan p'ye atamak, o bloğa giden tek göstericinin üzerine yazar, dolayısıyla asla serbest bırakılamaz - bir sızıntı ve tam da bellek zaten kıtken olan bir sızıntı. tmp'ye atayın, tmp'yi test edin, sonra kesinleştirin:
void *tmp = realloc(p, newsize);
if (tmp == NULL) {
/* p hâlâ geçerli; başarısızlığı çağırana uygun şekilde ele alın */
return 0;
}
p = tmp;
Bilmeye değer iki davranış daha: realloc(NULL, n) tam olarak malloc(n) gibi davranır; bu, bir büyütme fonksiyonunun ilk çağrısını özel durum olmadan ele almasını sağlar. Ve büyütmek yeni baytları ilk değersiz bırakır - yalnızca eski içerik korunur.
Bir Diziyi Büyütmek: Standart Kalıp
İşte neredeyse her C kapsayıcısının kullandığı kalıp - bir uzunluk, bir kapasite ve ikisi buluştuğunda ikiye katlama.
Burada ağırlığı iki tasarım noktası taşıyor.
Bir eklemek değil, ikiye katlamak. Sabit bir miktarda büyümek her eklemenin tüm diziyi kopyalamasına yol açar, dolayısıyla n eleman doldurmak kabaca n² bayt kopyalamaya mal olur. İkiye katlamak kopyalamaları, her eklemenin ortalamada sabit zaman almasına yetecek kadar seyrekleştirir. cap == 0 ? 4 : cap * 2 başlangıcı ilk eklemeyi ayrı bir dal olmadan halleder, çünkü realloc(NULL, n) zaten malloc'tur.
Yapı tek bir tahsisin sahibidir. v.data, yapının sahibi kimse onun tarafından tam olarak bir kez serbest bırakılır. Bu sahiplik kuralını yazıya dökmek - bir yorumda ya da her vec_init'i bir vec_free ile eşleyerek - büyüyen bir kapsayıcının fonksiyonlar arasında dolaşırken sızıntı yapmasını engelleyen şeydir.
Küçültme
Daha küçük bir boyutla realloc bir bloğu budar; bu, fazla büyük bir tampona bilinmeyen miktarda veri okuduktan sonra işe yarar:
Başarısız bir küçültme, uğruna programı sonlandırmaya değer bir hata değildir: eski, daha büyük blok hâlâ geçerli ve hâlâ doğrudur. Başarısızlığı yok saymanın doğru karar olduğu tek durum budur - ama yine de tmp üzerinden geçtiğine dikkat edin, çünkü yaşayan göstericinin üzerine yazmama kuralı değişmez.
Hangisine Uzanmalı
| İstediğiniz | Kullanın |
|---|---|
| Baytlar, içerik önemsiz | malloc(n * sizeof *p) |
| Sıfırlanmış bir eleman sayısı | calloc(n, sizeof *p) |
| Güvenilmeyen girdiden gelen bir sayı | calloc, taşma denetimi için |
| Elinizdeki bloğun daha büyüğü ya da küçüğü | Bir tmp göstericisi üzerinden realloc |
| Bir büyüme döngüsündeki ilk tahsis | realloc(NULL, n) - özel duruma gerek yok |
Dördü de aynı free ile, tam olarak bir kez serbest bırakılır.
Sık Yapılan Hatalar
p = realloc(p, n)- başarısızlıkta sızdırır. Her zamantmpüzerinden gidin.- Bloğa giden eski bir göstericiyi saklamak.
realloc'tan sonra veri taşınmış olabilir, dolayısıyla saklanan her iç gösterici ya da indeksten türetilmiş adres bayattır. İndeksler bir taşınmadan sağ çıkar; göstericiler çıkmaz. - Yeni baytların sıfır olduğunu varsaymak. Yalnızca
callocsıfırlar.reallocbüyüme bölgesini ilk değersiz bırakır. calloc(n * size, 1)- iki argümanlı biçimin var olma sebebi olan taşma denetimini çöpe atar.- Başarılı bir
realloc'tan sonra eski göstericiyi serbest bırakmak.realloconu zaten serbest bıraktı; ikinci birfreeçifte serbest bırakmadır.
Sıkça Sorulan Sorular
C'de calloc ile malloc arasındaki fark nedir?
İki fark. calloc(n, size) sayıyı ve eleman boyutunu ayrı ayrı alır ve bunların çarpımının taşmadığını denetler; malloc(n * size) ise sessizce sarabilir. Ve calloc bloğu sıfırla doldurur, malloc ise orada daha önce ne varsa onu bırakır.
C'de realloc nasıl çalışır?
realloc(p, newsize), iki boyuttan küçük olanına kadar mevcut içeriğiniz korunmuş olarak yeni boyutta bir blok döndürür. Bloğu yerinde büyütebilir ya da taşıyabilir, dolayısıyla kullanmaya devam edebileceğiniz tek gösterici döndürdüğü göstericidir - eskisi çoktan serbest bırakılmış olabilir.
realloc'u neden geçici bir göstericiye atamalısınız?
Çünkü çağrı başarısız olduğunda p = realloc(p, n), p'nin üzerine NULL yazar ve özgün blok hâlâ tahsis edilmiş, kendisini gösteren hiçbir şey olmadan kalır - bir sızıntı. Önce bir tmp'ye atayın, NULL için kontrol edin ve ancak ondan sonra p = tmp yazın.
malloc yerine ne zaman calloc kullanmalıyım?
Belleğin gerçekten sıfırlanmış olması gerektiğinde (bir sayaç dizisi, alanları boş başlaması gereken bir yapı, kısmen dolduracağınız bir tampon) ya da boyut, büyük olabilecek bir sayı çarpı eleman boyutu olduğunda. Zaten her baytın üzerine yazacaksanız malloc sıfırlama işini atlar.