C Bellek Yönetimi


90

C'de hafızayı nasıl yönettiğinizi gerçekten izlemeniz gerektiğini hep duymuşumdur. Ve hala C öğrenmeye başlıyorum, ama şimdiye kadar, ilgili aktiviteleri yönetmek için herhangi bir hafıza yapmak zorunda kalmadım .. Her zaman değişkenleri serbest bırakmak ve her türlü çirkin şeyler yapmak zorunda olduğumu hayal ettim. Ancak durum böyle görünmüyor.

Birisi bana (kod örnekleriyle) ne zaman "bellek yönetimi" yapmanız gerektiğine dair bir örnek gösterebilir mi?


Yanıtlar:


231

Değişkenlerin hafızaya alınabileceği iki yer vardır. Bunun gibi bir değişken oluşturduğunuzda:

Değişkenler " yığın " içinde oluşturulur . Yığın değişkenleri, kapsam dışına çıktıklarında (yani, kod artık onlara ulaşamadığında) otomatik olarak serbest bırakılır. Bunların "otomatik" değişkenler olarak adlandırıldığını duyabilirsiniz, ancak bu modası geçmiştir.

Birçok başlangıç ​​örneği yalnızca yığın değişkenlerini kullanır.

Yığın iyidir çünkü otomatiktir, ancak aynı zamanda iki dezavantajı vardır: (1) Derleyicinin değişkenlerin ne kadar büyük olduğunu önceden bilmesi gerekir ve (b) yığın alanı biraz sınırlıdır. Örneğin: Windows'ta, Microsoft bağlayıcı için varsayılan ayarlar altında, yığın 1 MB olarak ayarlanmıştır ve değişkenleriniz için tümü kullanılamaz.

Derleme zamanında dizinizin ne kadar büyük olduğunu bilmiyorsanız veya büyük bir diziye veya yapıya ihtiyacınız varsa, "B planına" ihtiyacınız vardır.

Plan B, " yığın " olarak adlandırılır . Genellikle İşletim Sisteminin izin verdiği büyüklükte değişkenler oluşturabilirsiniz, ancak bunu kendiniz yapmanız gerekir. Daha önceki gönderiler size bunu yapmanın bir yolunu gösterdi, ancak başka yollar da var:

(Yığındaki değişkenlerin doğrudan değil, işaretçiler aracılığıyla değiştirildiğini unutmayın)

Bir yığın değişkeni oluşturduğunuzda, sorun, derleyicinin onunla işiniz bittiğini söyleyememesi, bu nedenle otomatik yayınlamayı kaybedersiniz. İşte burada bahsettiğiniz "manuel serbest bırakma" devreye girer. Kodunuz artık değişkene ne zaman ihtiyaç duyulmayacağına karar vermekten ve onu serbest bırakmaktan sorumludur, böylece bellek başka amaçlar için alınabilir. Yukarıdaki durum için:

Bu ikinci seçeneği "iğrenç iş" yapan şey, değişkene artık ne zaman ihtiyaç duyulmadığını bilmenin her zaman kolay olmamasıdır. İhtiyacınız olmadığında bir değişkeni serbest bırakmayı unutmak, programınızın ihtiyaç duyduğu bellek miktarından daha fazlasını tüketmesine neden olur. Bu duruma "sızıntı" denir. "Sızan" bellek, programınız sona erene ve işletim sistemi tüm kaynaklarını kurtarana kadar hiçbir şey için kullanılamaz. Aslında onunla işiniz bitmeden bir yığın değişkenini yanlışlıkla serbest bırakırsanız daha kötü sorunlar bile mümkündür .

C ve C ++ 'da, yukarıda gösterildiği gibi yığın değişkenlerinizi temizlemekten sorumlusunuz. Ancak, Java ve .NET gibi C # gibi farklı bir yaklaşım kullanan ve yığının kendi kendine temizlendiği diller ve ortamlar vardır. "Çöp toplama" olarak adlandırılan bu ikinci yöntem, geliştirici için çok daha kolaydır, ancak ek yük ve performansta bir ceza ödersiniz. Bu bir denge.

(Daha basit ama umarım daha seviyeli bir cevap vermek için birçok detayı gözden geçirdim)


3
Yığın üzerine bir şey koymak istiyorsanız ancak derleme zamanında ne kadar büyük olduğunu bilmiyorsanız, alloca () yer açmak için yığın çerçevesini büyütebilir. Freea () yoktur, işlev döndüğünde tüm yığın çerçevesi açılır. Büyük tahsisler için alloca () kullanmak tehlikelerle doludur.
DGentry

1
Belki global değişkenlerin hafıza konumu hakkında bir veya iki cümle ekleyebilirsiniz
Michael Käfer

C'de asla dönüşü malloc(), nedeni UB, (char *)malloc(size);bkz. Stackoverflow.com/questions/605845/…
EsmaeelE

17

İşte bir örnek. Bir dizeyi çoğaltan bir strdup () işleviniz olduğunu varsayalım:

Ve buna şöyle diyorsun:

Programın çalıştığını görebilirsiniz, ancak belleği boşaltmadan (malloc aracılığıyla) bellek ayırdınız. Strdup'ı ikinci kez çağırdığınızda, işaretçinizi ilk bellek bloğuna kaybettiniz.

Bu küçük bellek miktarı için önemli bir sorun değil, ancak durumu bir düşünün:

Şu anda 11 gig bellek kullandınız (bellek yöneticinize bağlı olarak muhtemelen daha fazla) ve çökmediyseniz, işleminiz muhtemelen oldukça yavaş ilerliyor.

Düzeltmek için, malloc () ile elde edilen her şeyi kullanmayı bitirdikten sonra free () çağırmanız gerekir:

Umarım bu örnek yardımcı olur!


Bu cevabı daha çok beğendim. Ama küçük bir yan sorum var. Bunun gibi bir şeyin kitaplıklar ile çözülmesini beklerdim, temel veri türlerini yakından taklit eden ve bunlara bellek boşaltma özellikleri ekleyen ve böylece değişkenler kullanıldığında otomatik olarak serbest bırakılan bir kitaplık yok mu?
Lorenzo

Hiçbiri standardın parçası değildir. C ++ 'ya girerseniz, otomatik bellek yönetimi yapan dizeler ve kapsayıcılar elde edersiniz.
Mark Harrison

Anlıyorum, yani bazı 3. parti kitaplıklar var mı? Lütfen onlara isim verir misiniz?
Lorenzo

9

Yığın yerine yığın üzerinde bellek kullanmak istediğinizde "bellek yönetimi" yapmanız gerekir. Çalışma zamanına kadar bir dizinin ne kadar büyük olacağını bilmiyorsanız, o zaman yığını kullanmanız gerekir. Örneğin, bir dizede bir şey saklamak isteyebilirsiniz, ancak program çalıştırılana kadar içeriğinin ne kadar büyük olacağını bilmiyorsunuz. Bu durumda şöyle bir şey yazarsınız:


5

Bence C'deki işaretçinin rolünü göz önünde bulundurmak için soruyu yanıtlamanın en kısa yolu olduğunu düşünüyorum. İşaretçi, kendinizi ayağınızdan vurmanız için muazzam bir kapasite pahasına size büyük bir özgürlük veren hafif ama güçlü bir mekanizmadır.

C'de işaretçilerinizin sahip olduğunuz anıyı işaret etmesini sağlama sorumluluğu size aittir ve yalnızca sizindir. Etkili yazmayı zorlaştıran işaretçilerden vazgeçmediğiniz sürece, bu organize ve disiplinli bir yaklaşım gerektirir.

Tarihe gönderilen yanıtlar, otomatik (yığın) ve yığın değişken tahsislerine odaklanır. Yığın tahsisini kullanmak otomatik olarak yönetilen ve uygun bellek sağlar, ancak bazı durumlarda (büyük arabellekler, özyinelemeli algoritmalar) korkunç yığın taşması sorununa yol açabilir. Yığın üzerinde tam olarak ne kadar bellek ayırabileceğinizi bilmek, sisteme çok bağlıdır. Bazı katıştırılmış senaryolarda sınırınız birkaç düzine bayt olabilir, bazı masaüstü senaryolarında megabaytları güvenle kullanabilirsiniz.

Yığın tahsisi, dile daha az özgüdür. Temel olarak, siz onu iade etmeye ('ücretsiz') hazır olana kadar size belirli boyutta bir bellek bloğunun sahipliğini veren bir dizi kütüphane çağrısıdır. Kulağa basit geliyor, ancak anlatılmamış programcı kederiyle ilişkilendiriliyor. Problemler basittir (aynı belleği iki kez boşaltmak veya hiç [bellek sızıntıları], yeterli bellek ayırmamak [arabellek taşması], vb.), Ancak kaçınılması ve hata ayıklaması zordur. Uygulamada son derece disiplinli bir yaklaşım kesinlikle zorunludur, ancak elbette dil bunu zorunlu kılmaz.

Diğer gönderiler tarafından görmezden gelinen başka bir bellek ayırma türünden bahsetmek istiyorum. Değişkenleri herhangi bir işlevin dışında bildirerek statik olarak tahsis etmek mümkündür. Genel olarak bu tür bir tahsisin kötü bir şöhrete sahip olduğunu düşünüyorum çünkü küresel değişkenler tarafından kullanılıyor. Bununla birlikte, bu şekilde ayrılan belleği kullanmanın tek yolunun bir spagetti kodu karmaşasındaki disiplinsiz bir küresel değişken olduğunu söyleyen hiçbir şey yoktur. Statik ayırma yöntemi, yalnızca yığının bazı tuzaklarından ve otomatik ayırma yöntemlerinden kaçınmak için kullanılabilir. Bazı C programcıları, büyük ve sofistike C katıştırılmış ve oyun programlarının yığın ayırma kullanılmadan inşa edildiğini öğrenince şaşırırlar.


4

Burada hafızanın nasıl tahsis edilip boşaltılacağına dair bazı harika cevaplar var ve bence C'yi kullanmanın daha zor tarafı, kullandığınız tek hafızanın ayırdığınız hafıza olmasını sağlamaktır - eğer bu doğru şekilde yapılmazsa, sonlandırdığınız şey Bu sitenin kuzeni - bir arabellek taşması - ve başka bir uygulama tarafından kullanılan belleğin üzerine yazıyor ve çok öngörülemeyen sonuçlar veriyor olabilirsiniz.

Bir örnek:

Bu noktada, myString için 5 bayt ayırdınız ve onu "abcd \ 0" ile doldurdunuz (dizeler bir null - \ 0 ile biter). Dize ayırmanız

Programınıza ayırdığınız 5 bayta "abcde" atıyor olacaksınız ve sondaki boş karakter bunun sonuna konulacak - belleğin kullanımınız için ayrılmamış bir parçası olabilir ve ücretsiz, ancak aynı şekilde başka bir uygulama tarafından da kullanılıyor olabilir - Bu, bellek yönetiminin bir hatanın öngörülemeyen (ve bazen tekrarlanamayan) sonuçlarının olacağı kritik bir parçasıdır.


Burada 5 bayt ayırırsınız. Bir işaretçi atayarak onu gevşetin. Bu işaretçiyi serbest bırakmaya yönelik herhangi bir girişim, tanımsız davranışa yol açar. Not C-Dizeleri = operatörüne aşırı yükleme yapmaz, kopya yoktur.
Martin York

Yine de, gerçekten kullandığınız malloc'a bağlı. Birçok malloc operatörü 8 bayta hizalanır. Dolayısıyla, bu malloc bir üstbilgi / altbilgi sistemi kullanıyorsa, malloc 5 + 4 * 2 (hem üstbilgi hem de altbilgi için 4 bayt) ayırır. Bu 13 bayt olur ve malloc, hizalama için size fazladan 3 bayt verir. Bunu kullanmanın iyi bir fikir olduğunu söylemiyorum, çünkü sadece malloc'u bu şekilde çalışan sistemler olacaktır, ama en azından yanlış bir şey yapmanın neden işe yarayabileceğini bilmek önemlidir.
kodai

Loki: Cevabı strcpy()yerine kullanmak için düzenledim =; Sanırım Chris BC'nin niyeti buydu.
echristopherson

Modern platformların donanım belleği korumasının, kullanıcı alanı işlemlerinin diğer işlemlerin adres alanlarının üzerine yazmasını engellediğine inanıyorum; bunun yerine bir segmentasyon hatası alırsınız. Ama bu C'nin bir parçası değil.
echristopherson

4

Hatırlanması gereken bir şey , işaretçilerinizi her zaman NULL olarak başlatmaktır, çünkü başlatılmamış bir işaretçi, işaretçi hatalarının sessizce ilerlemesini sağlayabilecek sahte rasgele bir geçerli bellek adresi içerebilir. NULL ile başlatılacak bir göstericiyi zorlayarak, bu işaretçiyi başlatmadan kullanıp kullanmadığınızı her zaman yakalayabilirsiniz. Bunun nedeni, işletim sistemlerinin boş işaretçi kullanımını yakalamak için 0x00000000 sanal adresini genel koruma istisnalarına "bağlamaları".


2

Ayrıca, int [10000] gibi büyük bir dizi tanımlamanız gerektiğinde dinamik bellek ayırmayı kullanmak isteyebilirsiniz. Onu yığına koyamazsınız çünkü o zaman, hm ... bir yığın taşması yaşarsınız.

Bir başka iyi örnek, bir veri yapısının, örneğin bağlantılı liste veya ikili ağaç uygulaması olabilir. Buraya yapıştırmak için bir örnek kodum yok ama bunu kolayca google'da yapabilirsiniz.


2

(Yazıyorum çünkü şimdiye kadarki cevapların pek uygun olmadığını düşünüyorum.)

Hafıza yönetiminin bahsetmeye değer olmasının nedeni, karmaşık yapılar oluşturmanızı gerektiren bir probleminiz / çözümünüz olduğu zamandır. (Yığın üzerinde aynı anda çok fazla alan ayırırsanız programlarınız çökerse, bu bir hatadır.) Tipik olarak, öğrenmeniz gereken ilk veri yapısı bir çeşit listedir . İşte aklıma gelen tek bir bağlantılı bir tane:

Doğal olarak, birkaç başka işlev daha istersiniz, ancak temelde bu, bellek yönetimine ihtiyacınız olan şeydir. "Manuel" bellek yönetimi ile mümkün olan bir dizi numara olduğunu belirtmeliyim, örneğin,

  • Malloc'un (dil standardına göre) 4'e bölünebilen bir işaretçi döndürmesi garanti edildiği gerçeğini kullanarak ,
  • kendi uğursuz bir amaç için fazladan alan ayırmak,
  • bellek havuzu oluşturma s ..

İyi bir hata ayıklayıcı alın ... İyi şanslar!


Veri yapılarını öğrenmek, bellek yönetimini anlamada bir sonraki anahtar adımdır. Bu yapıları uygun şekilde çalıştırmak için algoritmaları öğrenmek, size bu acelecilerin üstesinden gelmek için uygun yöntemleri gösterecektir. Bu nedenle, aynı derslerde öğretilen Veri yapılarını ve Algoritmaları bulacaksınız.
aj.toulan

0

@ Euro Micelli

Eklenecek bir olumsuzluk, işlev döndüğünde yığına işaretçilerin artık geçerli olmamasıdır, bu nedenle bir işlevden bir yığın değişkenine bir işaretçi döndüremezsiniz. Bu yaygın bir hatadır ve yalnızca yığın değişkenleriyle başa çıkamamanızın ana nedenlerinden biridir. İşlevinizin bir işaretçi döndürmesi gerekiyorsa, o zaman malloc ve bellek yönetimi ile uğraşmanız gerekir.


0

@ Ted Percival :
... malloc () 'un dönüş değerini atmanıza gerek yok.

Tabii ki haklısın. Kontrol etmem gereken bir K&R kopyası olmasa da bunun her zaman doğru olduğuna inanıyorum .

C'deki örtük dönüştürmelerden pek hoşlanmıyorum, bu yüzden "sihir" i daha görünür kılmak için dökümleri kullanma eğilimindeyim. Bazen okunabilirliğe yardımcı olur, bazen yardım etmez ve bazen derleyici tarafından sessiz bir hatanın yakalanmasına neden olur. Yine de bu konuda şu ya da bu şekilde güçlü bir fikrim yok.

Derleyiciniz C ++ tarzı yorumları anlıyorsa, bu özellikle olasıdır.

Evet ... beni orada yakaladın. C ++ 'da C'den çok daha fazla zaman geçiriyorum. Bunu fark ettiğin için teşekkürler.


@echristopherson, teşekkürler. Haklısınız - ancak lütfen bu Soru / Cevap'ın, Stack Overflow'un genel Beta'da bile olmadığı Ağustos 2008'e ait olduğunu unutmayın. O zamanlar, sitenin nasıl çalışması gerektiğini hala düşünüyorduk. Bu Soru / Cevabın formatı, SO'nun nasıl kullanılacağına dair bir model olarak görülmemelidir. Teşekkürler!
Euro Micelli

Ah, bunu belirttiğiniz için teşekkürler - o zamanlar sitenin bu yönünün hala değişmekte olduğunu fark etmemiştim.
echristopherson 01

0

C'de aslında iki farklı seçeneğiniz var. Birincisi, sistemin hafızayı sizin için yönetmesine izin verebilirsiniz. Alternatif olarak, bunu kendiniz de yapabilirsiniz. Genellikle, olabildiğince uzun süre öncekine bağlı kalmak istersiniz. Ancak, C'deki otomatik yönetilen bellek son derece sınırlıdır ve aşağıdakiler gibi birçok durumda belleği manuel olarak yönetmeniz gerekecektir:

a. Değişkenin fonksiyonları aşmasını istiyorsunuz ve global değişkene sahip olmak istemiyorsunuz. ör .:

struct pair {
   int val;
   struct çifti * sonraki;
}

struct pair * new_pair (int val) {
   struct çifti * np = malloc (sizeof (struct pair));
   np-> val = değer;
   np-> sonraki = NULL;
   dönüş np;
}

b. dinamik olarak ayrılmış belleğe sahip olmak istiyorsunuz. En yaygın örnek, sabit uzunlukta olmayan dizidir:

int * özel_dizilim;
my_special_array = malloc (sizeof (int) * eleman_sayısı);
için (i = 0; i

c. GERÇEKTEN kirli bir şey yapmak istiyorsun. Örneğin, bir yapının birçok tür veriyi temsil etmesini isterdim ve birleşmeyi sevmiyorum (sendika çok dağınık görünüyor):

struct data { int veri_türü; long data_in_mem; }; struct hayvan {/ * bir şey * /}; struct person {/ * başka bir şey * /}; struct hayvan * read_animal (); struct kişi * read_person (); / * Esas olarak * / struct veri örneği; sampe.data_type = input_type; switch (input_type) { vaka DATA_PERSON: sample.data_in_mem = read_person (); kırmak; durum DATA_ANIMAL: sample.data_in_mem = read_animal (); varsayılan: printf ("Oh hoh! Sizi uyarıyorum, bu tekrar ve işletim sisteminizde hata yapacağım"); }

Bak, uzun bir değer HER ŞEYİ tutmak için yeterlidir. Sadece onu serbest bırakmayı unutma, yoksa pişman olacaksın. Bu, C: D'de eğlenmek için en sevdiğim püf noktaları arasında.

Bununla birlikte, genellikle favori numaralarınızdan (T___T) uzak durmak istersiniz. Çok sık kullanırsanız, er ya da geç işletim sisteminizi bozacaksınız. * Assign and free kullanmadığınız sürece, hala bakire olduğunuzu ve kodun hala güzel göründüğünü söylemek güvenlidir.


"Bakın, uzun bir değer HERHANGİ BİR ŞEYİ tutmak için yeterlidir" -: / neden bahsediyorsunuz, çoğu sistemde uzun bir değer 4 bayttır, tam olarak int ile aynıdır. Buradaki işaretleyicilere uymasının tek nedeni, uzun boyutunun işaretçi boyutuyla aynı olmasıdır. Yine de gerçekten void * kullanmalısınız.
Skor_26

-2

Elbette. İçinde kullandığınız kapsamın dışında var olan bir nesne yaratırsanız. İşte uydurma bir örnek (sözdizimimin kapalı olacağını unutmayın; C'm paslanmış, ancak bu örnek yine de kavramı gösterecektir):

Bu örnekte, MyClass'ın ömrü boyunca SomeOtherClass türünde bir nesne kullanıyorum. SomeOtherClass nesnesi birkaç işlevde kullanıldığından, belleği dinamik olarak ayırdım: SomeOtherClass nesnesi MyClass oluşturulduğunda oluşturulur, nesnenin ömrü boyunca birkaç kez kullanılır ve ardından MyClass serbest bırakıldığında serbest bırakılır.

Açıkçası, eğer bu gerçek kod olsaydı, myObject'i bu şekilde oluşturmak için (muhtemelen yığın bellek tüketiminin dışında) bir neden olmazdı, ancak bu tür nesne oluşturma / yok etme, çok sayıda nesneye sahip olduğunuzda ve hassas bir şekilde kontrol etmek istediğinizde yararlı hale gelir oluşturulduklarında ve yok edildiklerinde (örneğin, uygulamanız tüm ömrü boyunca 1 GB RAM emmemesi için) ve Pencereli bir ortamda bu, oluşturduğunuz nesneler (diyelim ki düğmeler) gibi hemen hemen zorunludur. , belirli bir işlevin (hatta sınıfın) kapsamının çok dışında var olması gerekir.


1
Heh, evet, bu C ++ değil mi? Birinin beni aramasının beş ay sürmesi şaşırtıcı.
TheSmurf
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.