Bir C programındaki her değişken bir yerde yaşar ve nerede olduğu sonradan değiştiremeyeceğiniz iki şeyi belirler: ne kadar yaşadığı ve ondan ne kadarına sahip olabileceğiniz. C size üç depolama bölgesi verir ve yanlış seçmek ya bir çökme ya bir sızıntı üretir. Bu sayfa onları serip ömürleri yanlış anlamaktan doğan klasik hatayı gösteriyor.
Üç Bölge
yuksek adresler
+---------------------------+
| yigin (stack) | yereller, parametreler, donus adresleri
| asagi dogru buyur | | donuste otomatik serbest birakilir
| v |
+---------------------------+
| (kullanilmayan) |
+---------------------------+
| ^ |
| yukari dogru buyur| |
| obek (heap) | malloc / calloc / realloc bloklari
+---------------------------+ yalnizca free() ile birakilir
| statik / kuresel veri | kureseller ve staticler, tum calisma
+---------------------------+
| kod (text) | makine kodu, salt okunur
+---------------------------+
dusuk adresler
- Otomatik depolama (yığın), fonksiyon parametrelerini ve static olmayan yerelleri tutar. Bir fonksiyona girildiğinde bir yığın bloğu talep edilir ve döndüğünde serbest bırakılır. Boyut derleme zamanında sabittir.
- Dinamik depolama (öbek),
malloc,callocverealloc'tan gelen her şeyi tutar. Boyut çalışma zamanında belirlenir; ömür yalnızcafreeile biter. - Statik depolama, küreselleri ve
staticbildirilen her şeyi tutar. Programın tüm çalışması boyunca var olur vemainbaşlamadan önce sıfırla başlatılır.
Diyagramdaki adresler alışıldık düzendir, bir garanti değil - standart yerleşimi değil ömürleri tarif eder.
İş Başında Otomatik Depolama
demo'ya yapılan her çağrı taze bir local ve taze bir table alır. Hiçbir şey elle serbest bırakılmaz, hiçbir şey sızamaz ve tahsis, yığın göstericisini kaydıran tek bir komuta mal olur. Sıradan yerellerin varsayılanınız olması gerekmesinin sebebi budur: C'nin sahip olduğu en hızlı ve en güvenli depolamadır.
Püf noktası kapanış parantezidir. O çalıştığında o bellek gitmiştir.
Sarkan Gösterici
İşte her C programcısının bir kez yazdığı hata:
/* BOZUK: artık var olmayan belleğin adresini döndürür */
int *make_counter(void) {
int count = 0;
return &count; /* count bu parantezde ölür */
}
int main(void) {
int *p = make_counter();
*p = 5; /* ölü bir yığın çerçevesine yazma */
return 0;
}
&count, make_counter çalışırken gayet geçerli bir adresti. Dönüşte o yığın alanı sıradaki hangi fonksiyon çağrılırsa ona verilir, dolayısıyla p artık başka birinin yerel değişkenlerinin içini gösterir. Okumak çöp verir; yazmak onları bozar. GCC ve Clang tam olarak bu biçim için uyarır (-Wreturn-local-addr), o yüzden uyarılar açık derleyin.
Aynı hata dizilerle kılık değiştirir ve orada uyarı çoğu zaman devreye girmez:
O fonksiyonun bozuk sürümü metni yerel bir char buf[64] içinde kurup return buf; yapardı - aynı anda var olmayı bırakan bir tamponun adresini döndürerek.
Bunu Düzeltmenin Üç Yolu
1. Tamponu çağıran sağlar (yukarıda gösterildi). Tahsis yok, sahiplik sorusu yok ve C kütüphanelerinde en yaygın tarz. Fonksiyon, içinde kalabilmek için boyutu alır.
2. Öbek belleği döndürün ve kimin serbest bırakacağını söyleyin.
Öbek bloğu tasarım gereği fonksiyondan uzun yaşar - dinamik belleğin tüm meselesi budur. Bedeli, sahiplik yorumu ve çağıranın free'sidir.
3. Tek bir paylaşılan tampon kabul edilebilir olduğunda statik depolama kullanın:
Bir fonksiyonun içindeki static, değişkenin kapsamını yerel tutarken ona programın ömrünü verir, dolayısıyla adresini döndürmek geçerlidir. Takas, ondan yalnızca bir tane olmasıdır: her çağıran onu paylaşır ki bu, kalıbı iş parçacıklı kodda kullanılamaz ve iki çağıran göstericiyi aynı anda tuttuğunda tek iş parçacıklı kodda bile şaşırtıcı kılar.
Boyut: Yığının Tükendiği Yer
Yığın alanı küçük ve sabittir. Ana iş parçacığı tipik olarak Windows'ta 1 MB, Linux'ta 8 MB alır; oluşturulan bir iş parçacığı çoğu zaman çok daha az alır. Öbek, sistemin mevcut belleğiyle sınırlıdır.
void bad(void) {
int huge[1000000]; /* ~4 MB yığın - girişte muhtemelen çöker */
huge[0] = 1;
}
Hiçbir tanı mesajı ve denetlenecek bir NULL yoktur: program, gövdenin ilk satırı çalışmadan önce, genellikle bir segmentation fault ile ölür. Öbek sürümü başarısızlığı düzgün biçimde bildirir:
Derin özyineleme yığını aynı şekilde, her seferinde bir çerçeve olmak üzere tüketir - kaçak bir özyinelemeli fonksiyon, pratikte yığın taşmasının en yaygın sebebidir.
Maliyet ve Yerellik
Yığın tahsisi bir yazmaç üzerinde tek bir aritmetik işlemdir. Öbek tahsisi, uygun bir blok arayan, kilit alabilen ve zaman zaman işletim sisteminden daha fazla bellek isteyen bir kütüphane çağrısıdır. Sıcak bir döngüde o fark ölçülebilirdir.
Yığın verisi ayrıca derli toplu ve yakın zamanda dokunulmuştur, dolayısıyla önbellekte olma eğilimindedir. Öbek blokları dağınık olabilir. Bu olguların hiçbiri tek başına bir tasarımı yönlendirmemelidir - ömrün doğruluğu önce gelir - ama ikisi de doğru olan iki tasarım arasında yığın olanı genellikle daha hızlıdır.
Bölgeleri Görmek
Adresleri yazdırmak yerleşimi somutlaştırır. Kesin değerler her çalıştırmada farklıdır (modern sistemler onları rastgeleleştirir), ama gruplanma görünürdür:
Küresel ile statik yan yana durur; öbek bloğu başka yerdedir; yerel tipik olarak ikisinden de uzaktır. %p için void *'a dönüştürün - biçim belirtecinin istediği budur.
Seçmek
Yığını şu durumlarda kullanın:
- boyut derleme zamanında biliniyorsa,
- veri yalnızca bu fonksiyonun ve çağırdıklarının içinde gerekiyorsa,
- ve küçükse - megabaytlar değil birkaç kilobayt.
Öbeği şu durumlarda kullanın:
- boyut girdiye, bir dosyaya ya da bir hesaba bağlıysa,
- verinin onu oluşturan fonksiyondan uzun yaşaması gerekiyorsa,
- ya da yığın sınırını tehdit edecek kadar büyükse.
Statiği şu durumlarda kullanın:
- tüm program için tam olarak bir örnek var olmalıysa,
- ve onu tüm çağıranlar arasında paylaşmak gerçekten doğruysa.
Varsayılan yığındır. Öbeğin üç sebebinden biri geçerli olduğunda ona uzanın ve uzandığınızda, ömür kazandırdığınız bloğun yine de serbest bırakılması için bellek sızıntıları sayfasındaki sahiplik kurallarına uyun.
Ayna Görüntüsü İki Hata
Onları birlikte adlandırmaya değer, çünkü aynı ömür sorusunun iki farklı cevabıdır:
- Sarkan gösterici - bellek göstericiden önce öldü.
&localdöndürmek ya dafree'den sonra bir göstericiyi kullanmak. Program artık başka bir şeye ait olan depolamayı okur ya da yazar. - Bellek sızıntısı - gösterici bellekten önce öldü. Bir
mallocbloğuna giden son referansı kaybetmek. Hemen hiçbir şey bozulmaz; süreç yalnızca büyür.
İkisi de verinin ne kadar yaşaması gerektiği ile onu hangi bölgeye koyduğunuz arasındaki bir uyumsuzluktan gelir. Önce ömre karar verin, bölge onu izler.
Sıkça Sorulan Sorular
C'de yığın ile öbek arasındaki fark nedir?
Yığın yerel değişkenleri tutar: derleyici onları boyutlandırır, bir fonksiyona girildiğinde oluşturulur ve döndüğünde yok edilirler ve tahsis hiçbir şeye mal olmaz. Öbek malloc bloklarını tutar: boyutu çalışma zamanında siz seçersiniz, blok siz free edene dek yaşar ve tahsisin gerçek bir maliyeti vardır.
C'de yerel bir değişkene neden gösterici döndüremem?
Çünkü yerelin depolaması fonksiyon döndüğü anda serbest bırakılır. Gösterici hâlâ o adresi tutar, ama bellek artık bir sonraki fonksiyon çağrısına aittir - onu okumak çöp verir, yazmak alakasız veriyi bozar. Bu sarkan bir göstericidir. Bir malloc bloğu döndürün ya da tamponu çağıranın sağlamasını isteyin.
C'de yığın ne kadar büyüktür?
Ana iş parçacığı için tipik olarak 1 ila 8 MB ve ek iş parçacıkları için çok daha azdır - yerel olarak int big[1000000];in genellikle girişte programı çökertecek kadar küçük. Öbek, mevcut sistem belleğiyle sınırlıdır, dolayısıyla büyük ya da boyutu bilinmeyen veri oraya aittir.
C'de yığın yerine ne zaman öbek kullanmalıyım?
Üç durumda: boyut çalışma zamanına kadar bilinmiyorsa, verinin onu oluşturan fonksiyondan uzun yaşaması gerekiyorsa ya da blok yığın için çok büyükse (kabaca birkaç yüz kilobaytın üzerindeki her şey). Diğer her şey düz bir yerel olmalıdır - daha hızlıdır ve sızdıramaz.