C ++ [kapalı] bellek sızıntılarını önlemek için genel yönergeler


130

C ++ programlarında belleği sızdırmadığımdan emin olmak için bazı genel ipuçları nelerdir? Dinamik olarak tahsis edilmiş hafızayı kimin boşaltması gerektiğini nasıl anlayabilirim?


26
Bana oldukça yapıcı geliyor.
Shoerob

11
Bu yapıcıdır. Cevaplar gerçekler, uzmanlıklar, referanslar vb. Tarafından desteklenir. Ve olumlu oyların / cevapların sayısını görün .. !!
Samitha Chathuranga

Yanıtlar:



200

RAII ve akıllı işaretçiler hakkındaki tüm tavsiyeleri tamamen onaylıyorum, ancak biraz daha yüksek seviyeli bir ipucu da eklemek istiyorum: yönetilmesi en kolay bellek, asla ayırmadığınız bellektir. Hemen hemen her şeyin bir referans olduğu C # ve Java gibi dillerin aksine, C ++ 'da mümkün olduğunca nesneler yığına koymalısınız. Birkaç kişinin (Dr Stroustrup dahil) işaret ettiği gibi, C ++ 'da çöp toplamanın hiç popüler olmamasının ana nedeni, iyi yazılmış C ++' nın ilk etapta fazla çöp üretmemesidir.

Yazma

Object* x = new Object;

ya da

shared_ptr<Object> x(new Object);

ne zaman yazabilirsin

Object x;

34
Keşke buna +10 verebilseydim. Bugün çoğu C ++ programcısında gördüğüm en büyük sorun bu ve bunun Java'yı C ++ 'dan önce öğrenmelerinden kaynaklandığını varsayıyorum.
Kristopher Johnson

Çok ilginç nokta - neden C ++ bellek yönetimi sorunlarımın diğer dillerden çok daha az olduğunu merak etmiştim, ama şimdi nedenini anlıyorum: aslında vanilya C'de olduğu gibi yığın üzerinde şeylerin gitmesine izin veriyor.
ArtOfWarfare

Peki Nesne x yazarsanız ne yaparsınız; ve sonra x'i atmak mı istiyorsunuz? diyelim ki x ana yöntemde yaratıldı.
Yamcha

3
@ user1316459 C ++, anında kapsamlar oluşturmanıza da olanak tanır. Tek yapmanız gereken, x'in yaşam süresini şu şekilde parantez içine almaktır: {Nesne x; x.DoSomething; }. Son '}' den sonra, x'in yıkıcısı, içerdiği tüm kaynakları serbest bırakarak çağrılacaktır. Eğer x, kendisi yığın üzerinde tahsis edilecek bellek ise, onu, kolay ve uygun bir şekilde temizlenebilmesi için benzersiz bir_ptr'ye sarmanızı öneririm.
David Peterson

1
Robert: evet. Ross demedi "Hiçbir zaman yazma [kodu, yeni içeren]", o "[yani] Do not yazma sözü sen varken sadece [yığını üzerine koydu]". Yığın üzerindeki büyük nesneler, özellikle performans yoğun kodlar için çoğu durumda doğru arama olmaya devam edecektir.
codetaku

104

RAII kullan

  • Çöp Toplamayı Unutun (Bunun yerine RAII kullanın). Çöp Toplayıcının bile sızıntı yapabileceğini (Java / C # 'da bazı referansları "geçersiz kılmayı unutursanız) ve Çöp Toplayıcının kaynakları elden çıkarmanıza yardımcı olmayacağını unutmayın (bir tutamaç edinmiş bir nesneniz varsa bir dosya varsa, nesne kapsam dışına çıktığında, bunu Java'da elle yapmazsanız veya C # 'da "dispose" kalıbını kullanırsanız dosya otomatik olarak serbest bırakılmaz).
  • "İşlev başına bir dönüş" kuralını unutun . Bu, sızıntıları önlemek için iyi bir C tavsiyesidir, ancak istisnaları kullanması nedeniyle C ++ 'da güncelliğini yitirmiştir (bunun yerine RAII kullanın).
  • Ve "Sandviç Modeli" iyi bir C tavsiyesi olsa da , istisnaların kullanımı nedeniyle C ++ 'da güncelliğini yitirmiştir (bunun yerine RAII kullanın).

Bu gönderi tekrarlı gibi görünüyor, ancak C ++ 'da bilinmesi gereken en temel kalıp RAII'dir .

Hem boost, TR1 hem de düşük (ancak genellikle yeterince verimli) auto_ptr'den (ancak sınırlamalarını bilmelisiniz) akıllı işaretçileri kullanmayı öğrenin.

RAII, C ++ 'da hem istisnai güvenlik hem de kaynak kullanımının temelidir ve başka hiçbir model (sandviç vb.) Size ikisini birden vermez (ve çoğu zaman size hiçbir şey vermez).

Aşağıda RAII ve RAII olmayan kodun karşılaştırmasına bakın:

void doSandwich()
{
   T * p = new T() ;
   // do something with p
   delete p ; // leak if the p processing throws or return
}

void doRAIIDynamic()
{
   std::auto_ptr<T> p(new T()) ; // you can use other smart pointers, too
   // do something with p
   // WON'T EVER LEAK, even in case of exceptions, returns, breaks, etc.
}

void doRAIIStatic()
{
   T p ;
   // do something with p
   // WON'T EVER LEAK, even in case of exceptions, returns, breaks, etc.
}

Hakkında de ray

Özetlemek gerekirse ( Ogre Mezmur 33'ün yorumundan sonra ), RAII üç kavrama dayanır:

  • Nesne bir kez inşa edildiğinde, sadece çalışır! Yapıcıdaki kaynakları edinin.
  • Nesne imhası yeterli! Yıkıcıda ücretsiz kaynaklar yapın.
  • Her şey kapsamlarla ilgili! Kapsamlı nesneler (yukarıdaki doRAIIStatic örneğine bakın) bildirimlerinde oluşturulacak ve çıkış nasıl olursa olsun (dönüş, kesme, istisna, vb.) Yürütme kapsamdan çıktığı anda yok edilecektir.

Bu, doğru C ++ kodunda çoğu nesnenin oluşturulmayacağı newve bunun yerine yığın üzerinde bildirileceği anlamına gelir . Ve kullanılarak inşa edilenler için new, tümü bir şekilde kapsamlı olacaktır (örneğin, akıllı bir işaretçiye iliştirilmiş).

Bir geliştirici olarak, bu gerçekten çok güçlüdür, çünkü manuel kaynak işlemeyi önemsemeniz gerekmeyecektir (C'de yapıldığı gibi veya Java'daki bazı nesneler için try/ finallybu durumda yoğun şekilde kullanılır ) ...

Düzenle (2012-02-12)

"kapsamlı nesneler ... çıkış ne olursa olsun ... yok edilecek" bu tamamen doğru değil. RAII'yi aldatmanın yolları vardır. herhangi bir terminate () çeşidi temizlemeyi atlayacaktır. exit (EXIT_SUCCESS) bu bağlamda bir tezattır.

- wilhelmtell

wilhelmtell bu konuda oldukça haklı: RAII'yi aldatmak için istisnai yollar vardır , bunların hepsi sürecin aniden durmasına neden olur.

Bunlar istisnai yollardır çünkü C ++ kodu sonlandırma, çıkış vb. İle karıştırılmamıştır veya istisnalar olması durumunda, işlenmemiş bir istisna istiyoruz. , işlemin çökmesi ve çekirdeğin bellek görüntüsünü temizledikten sonra değil olduğu gibi çökertmesi için .

Ancak yine de bu vakaları bilmeliyiz çünkü nadiren olsa da yine de olabilirler.

( C ++ kodunu kim arıyor terminateya exitda sıradan mı? ... GLUT ile oynarken bu problemle uğraşmak zorunda olduğumu hatırlıyorum : Bu kütüphane, C ++ geliştiricileri için işleri önemsememek gibi zorlaştıracak şekilde aktif olarak tasarlamaya kadar çok C odaklıdır. Yığın ayrılmış veriler hakkında veya ana döngülerinden asla geri dönmemek konusunda "ilginç" kararlar almak hakkında ... Bu konuda yorum yapmayacağım) .


DoRAIIStatic () 'in belleği sızdırmadığından emin olmak için T sınıfının RAII kullanması gerekmez mi? Örneğin T p (); p.doSandwich (); Bununla ilgili pek bir şey bilmiyorum.
Daniel O

@Ogre Mezmur33: Yorumunuz için teşekkürler. Tabii ki haklısın. RAII Wikipedia sayfasına her iki bağlantıyı ve RAII'nin ne olduğunun küçük bir özetini ekledim.
paercebal

1
@Shiftbit: Tercih sırasına göre üç yol: _ _ _ 1. Gerçek nesneyi STL kabının içine koyun. _ _ _ 2. STL kabının içine nesnelerin akıllı işaretçileri (paylaşılan_tr) koyun. _ _ _ 3. STL kabının içine ham işaretçiler koyun, ancak verilere herhangi bir erişimi kontrol etmek için kabı sarın. Sarmalayıcı, yıkıcının ayrılmış nesneleri serbest bırakmasını sağlayacak ve sarmalayıcı erişimcileri, konteynere erişirken / değiştirirken hiçbir şeyin bozulmadığından emin olacaktır.
paercebal

1
@Robert: C ++ 03'te doRAIIDynamic'i bir alt veya üst işleve (veya genel kapsam) sahiplik vermesi gereken bir işlevde kullanırsınız. Veya bir fabrika aracılığıyla bir polimorf nesneye arabirim aldığınızda (doğru yazılmışsa akıllı bir işaretçiyi döndürür). C ++ 11'de durum daha azdır çünkü nesnenizi taşınabilir hale getirebilirsiniz, bu nedenle yığın üzerinde bildirilen bir nesnenin sahipliğini vermek daha kolaydır ...
paercebal

2
@Robert: ... Yığın üzerinde bir nesnenin bildirilmesinin, o nesnenin dahili olarak öbeği kullanmadığı anlamına gelmediğini unutmayın (çifte olumsuzlamaya dikkat edin ... :-) ...). Örneğin, Küçük Dizgi Optimizasyonu ile uygulanan std :: string, küçük dizeler (~ 15 karakter) için "sınıfın yığınında" bir arabelleğe sahip olacak ve daha büyük dizeler için öbek içindeki belleğe bir işaretçi kullanacaktır ... Ancak dışarıdan bakıldığında, std :: string hala yığın üzerinde bildirdiğiniz (genellikle) ve bir tamsayı kullandığınız gibi kullandığınız bir değer türüdür (bunun tersine: bir polimorf sınıfı için bir arabirim kullanacağınız gibi).
paercebal

25

Aşağıdaki gibi akıllı işaretçiler, bakmak isteyeceksiniz Boost akıllı işaretçiler .

Onun yerine

int main()
{ 
    Object* obj = new Object();
    //...
    delete obj;
}

boost :: shared_ptr, referans sayısı sıfır olduğunda otomatik olarak silinir:

int main()
{
    boost::shared_ptr<Object> obj(new Object());
    //...
    // destructor destroys when reference count is zero
}

Son notuma dikkat edin, "referans sayısı sıfır olduğunda, bu en havalı kısımdır. Dolayısıyla, nesnenizin birden fazla kullanıcısına sahipseniz, nesnenin hala kullanımda olup olmadığını takip etmek zorunda kalmayacaksınız. Hiç kimse sizin paylaşılan işaretçi yok edilir.

Ancak bu her derde deva değil. Temel işaretçiye erişebilmenize rağmen, ne yaptığından emin olmadığınız sürece onu bir 3. parti API'ye geçirmek istemezsiniz. Çoğu zaman, kapsam oluşturma bittikten SONRA yapılacak işler için başka bir iş parçacığına "gönderdiğiniz" şeyler. Bu, Win32'deki PostThreadMessage ile ortaktır:

void foo()
{
   boost::shared_ptr<Object> obj(new Object()); 

   // Simplified here
   PostThreadMessage(...., (LPARAM)ob.get());
   // Destructor destroys! pointer sent to PostThreadMessage is invalid! Zohnoes!
}

Her zaman olduğu gibi, düşünme kapınızı herhangi bir aletle kullanın ...



11

Bellek sızıntılarının çoğu, nesne sahipliği ve kullanım ömrü konusunda net olmamanın sonucudur.

Yapmanız gereken ilk şey, mümkün olduğunca Yığın üzerinde ayırmaktır. Bu, bir amaç için tek bir nesne ayırmanız gereken çoğu durumla ilgilidir.

Bir nesneyi 'yenileştirmeniz' gerekiyorsa, çoğu zaman ömrünün geri kalanında tek bir açık sahibi olacaktır. Bu durum için, işaretçi tarafından içlerinde depolanan nesnelere 'sahip olmak' için tasarlanmış bir dizi koleksiyon şablonu kullanma eğilimindeyim. STL vektörü ve harita kapsayıcıları ile uygulanırlar ancak bazı farklılıkları vardır:

  • Bu koleksiyonlar kopyalanamaz veya atanamaz. (nesneler içerdiklerinde.)
  • Nesnelerin işaretçileri bunlara eklenir.
  • Koleksiyon silindiğinde, yıkıcı önce koleksiyondaki tüm nesneler için çağrılır. (İmha edildiğini ve boş olmadığını iddia ettiği başka bir sürümüm var.)
  • İşaretçileri sakladıkları için, devralınan nesneleri de bu kaplarda saklayabilirsiniz.

STL ile aradığım şey, çoğu uygulamada nesneler, bu kaplarda kullanım için gerekli olan anlamlı kopya anlamlarına sahip olmayan benzersiz varlıklar iken, Değer nesnelerine çok odaklanmış olmasıdır.


10

Bah, siz küçük çocuklar ve yeni moda çöp toplayıcılarınız ...

"Mülkiyet" konusunda çok güçlü kurallar - yazılımın hangi nesnesinin veya bölümünün nesneyi silme hakkına sahip olduğu. Bir işaretçinin "sahibi" veya "sadece bak, dokunma" olup olmadığını anlamak için yorumları ve akıllı değişken adlarını temizleyin. Kimin neye sahip olduğuna karar vermeye yardımcı olmak için, her alt program veya yöntemdeki "sandviç" modelini olabildiğince izleyin.

create a thing
use that thing
destroy that thing

Bazen çok farklı yerlerde yaratmak ve yok etmek gerekir; Bundan kaçınmanın zor olduğunu düşünüyorum.

Karmaşık veri yapıları gerektiren herhangi bir programda, "sahip" işaretçileri kullanarak diğer nesneleri içeren kesin bir nesne ağacı oluşturuyorum. Bu ağaç, uygulama alanı kavramlarının temel hiyerarşisini modeller. Örnek bir 3B sahnenin nesneleri, ışıkları, dokuları vardır. Oluşturmanın sonunda program kapandığında, her şeyi yok etmenin açık bir yolu var.

Diğer birçok işaretçi, bir varlığın diğerine erişmesi gerektiğinde, gecikmeleri veya her neyse taramak için gerektiği gibi tanımlanır; bunlar "sadece bakma" dır. 3B sahne örneği için - bir nesne bir doku kullanır ancak kendisine ait değildir; diğer nesneler aynı dokuyu kullanabilir. Bir nesnenin imha etmez olmayan bir doku yıkımı çağırır.

Evet zaman alıyor ama ben öyle yapıyorum. Nadiren bellek sızıntılarım veya başka sorunlar yaşıyorum. Ancak daha sonra, yüksek performanslı bilimsel, veri toplama ve grafik yazılımlarının sınırlı alanında çalışıyorum. Bankacılık ve e-ticaret, olay odaklı GUI'ler veya yüksek ağa sahip eşzamansız kaos gibi işlemleri sık sık yapmıyorum. Belki de yeni moda yöntemlerin burada bir avantajı vardır!


Tamamen katılıyorum. Gömülü bir ortamda çalışmak, üçüncü taraf kitaplık lüksüne de sahip olmayabilirsiniz.
simon

6
Katılmıyorum. "o şeyi kullan" bölümünde, bir dönüş veya istisna atılırsa, ayrılmayı kaçırırsınız. Performansa gelince, std :: auto_ptr size hiçbir maliyeti olmaz. Asla senin yaptığın gibi kodlamadığımdan değil. Sadece% 100 ve% 99 güvenli kod arasında bir fark vardır. :-)
paercebal

8

Harika soru!

c ++ kullanıyorsanız ve gerçek zamanlı CPU ve bellek boud uygulaması geliştiriyorsanız (oyunlar gibi) kendi Bellek Yöneticinizi yazmanız gerekir.

Bence çeşitli yazarların bazı ilginç çalışmalarını birleştirmek ne kadar iyi olursa, size bir ipucu verebilirim:

  • Sabit boyutlu ayırıcı, ağın her yerinde yoğun bir şekilde tartışılıyor

  • Küçük Nesne Tahsisi, Alexandrescu tarafından 2001 yılında mükemmel kitabı "Modern c ++ tasarımı" nda tanıtıldı.

  • Dimitar Lazarov tarafından yazılan "Yüksek Performanslı Yığın ayırıcı" adlı Game Programming Gem 7'deki (2008) harika bir makalede büyük bir gelişme (kaynak kodu dağıtılmış olarak) bulunabilir.

  • Bu makalede harika bir kaynak listesi bulunabilir

Kendi başınıza işe yaramaz bir ayırıcı yazmaya başlamayın ... Önce KENDİNİZİ BELGELEYİN.


5

C ++ 'da bellek yönetimi ile popüler hale gelen tekniklerden biri RAII'dir . Temel olarak, kaynak tahsisini idare etmek için yapıcıları / yıkıcıları kullanırsınız. Elbette, istisna güvenliği nedeniyle C ++ 'da başka iğrenç ayrıntılar da var, ancak temel fikir oldukça basit.

Sorun genellikle mülkiyet meselesine bağlı. Scott Meyers'in Etkili C ++ serisini ve Andrei Alexandrescu'nun Modern C ++ Tasarımını okumanızı şiddetle tavsiye ederim.



4

Kullanabileceğiniz her yerde akıllı işaretçiler kullanın! Hafıza sızıntılarının tümü ortadan kaybolur.


4

Projeniz genelinde bellek sahipliği kurallarını paylaşın ve bilin. COM kurallarını kullanmak en iyi tutarlılığı sağlar ([in] parametreler arayan kişiye aittir, arayan uç kopyalamalıdır; [dışarı] parametreler arayan kişiye aittir, arayan uç bir referans tutuyorsa bir kopya çıkarmalıdır; vb.)


4

valgrind , programlarınızın bellek sızıntılarını çalışma zamanında da kontrol etmek için iyi bir araçtır.

Linux'un çoğu çeşidinde (Android dahil) ve Darwin'de mevcuttur.

Programlarınız için birim testleri yazmak için kullanırsanız, testler üzerinde sistematik olarak valgrind çalıştırma alışkanlığı edinmelisiniz. Erken bir aşamada birçok bellek sızıntısını önleyecektir. Ayrıca, tam bir yazılımda olduğu gibi basit testlerde bunları belirlemek genellikle daha kolaydır.

Elbette bu tavsiye, diğer herhangi bir hafıza kontrol aracı için geçerli kalır.


3

Ayrıca, bir std kütüphane sınıfı (örneğin vektör) varsa, manuel olarak ayrılmış belleği kullanmayın. Sanal bir yıkıcıya sahip olduğunuz kuralı ihlal edip etmediğinizden emin olun.


2

Bir şey için akıllı bir işaretçi kullanamıyorsanız / kullanmıyorsanız (bu çok büyük bir kırmızı bayrak olmalıdır), kodunuzu şu şekilde yazın:

allocate
if allocation succeeded:
{ //scope)
     deallocate()
}

Bu açık, ancak kapsamda herhangi bir kod yazmadan önce yazdığınızdan emin olun.


2

Bu hataların sık görülen bir kaynağı, bir nesneye bir referans veya işaretçi kabul eden, ancak sahipliği belirsiz bırakan bir yönteme sahip olduğunuz zamandır. Stil ve yorum kuralları bunu daha az olası hale getirebilir.

İşlevin nesnenin sahipliğini aldığı durum özel durum olsun. Bunun olduğu her durumda, başlık dosyasındaki işlevin yanına bunu belirten bir yorum yazdığınızdan emin olun. Çoğu durumda, bir nesneyi tahsis eden modülün veya sınıfın, onu serbest bırakmaktan da sorumlu olduğundan emin olmak için çabalamalısınız.

Const kullanmak bazı durumlarda çok yardımcı olabilir. Bir işlev bir nesneyi değiştirmeyecekse ve ona döndükten sonra da devam eden bir başvuru kaydetmiyorsa, bir const başvurusunu kabul edin. Arayanın kodunu okuduktan sonra, işlevinizin nesnenin sahipliğini kabul etmediği anlaşılacaktır. Aynı işlevin const olmayan bir işaretçiyi kabul etmesini sağlayabilirsiniz ve arayan uç, aranan ucun sahipliği kabul ettiğini varsayabilir veya varsaymayabilir, ancak const referansı ile soru yoktur.

Bağımsız değişken listelerinde const olmayan başvurular kullanmayın. Arayanın kodunu okurken, aranan ucun parametreye bir referans tutmuş olabileceği çok açık değildir.

Referans sayılan işaretçileri tavsiye eden yorumlara katılmıyorum. Bu genellikle iyi çalışır, ancak bir hatanız olduğunda ve çalışmadığında, özellikle yıkıcınız çok iş parçacıklı bir programda olduğu gibi önemsiz olmayan bir şey yaparsa. Kesinlikle tasarımınızı çok zor değilse referans saymaya ihtiyaç duymayacak şekilde ayarlamaya çalışın.


2

Önem Sırasına Göre İpuçları:

-İpucu # 1 Yıkıcılarınızı "sanal" ilan etmeyi her zaman unutmayın.

-İpucu # 2 RAII kullanın

-İpucu 3 Boost'un akıllı işaretleyicilerini kullanın

-İpucu # 4 Kendi buggy Smartpointer'larınızı yazmayın, boost kullanın (şu anda bulunduğum bir projede boost kullanamıyorum ve kendi akıllı işaretçilerimde hata ayıklamak zorunda kaldım, kesinlikle kabul etmem yine aynı rota, ancak şimdi yine bağımlılıklarımıza destek ekleyemiyorum)

-İpucu # 5 Bazı sıradan / performans dışı kritik (binlerce nesneli oyunlarda olduğu gibi) çalışıyorsa, Thorsten Ottosen'in artırma işaretçisi kabına bakın

-İpucu # 6 Görsel Kaçak Tespiti'nin "vld" başlığı gibi tercih ettiğiniz platform için bir sızıntı tespit başlığı bulun


Bir numara kaçırıyor olabilirim, ancak 'oyun' ve 'performans açısından kritik olmayan' nasıl aynı cümle içinde olabilir?
Adam Naylor

Oyunlar elbette kritik senaryoya bir örnektir. Orada netlik kazanmamış olabilir
Robert Gould

İpucu # 1 yalnızca sınıfın en az bir sanal yöntemi varsa uygulanmalıdır. Bir polimorfik miras ağacında temel sınıf olarak hizmet etmesi amaçlanmayan bir sınıfa asla işe yaramaz bir sanal yıkıcı empoze etmem.
antred

1

Mümkünse, boost shared_ptr ve standart C ++ auto_ptr kullanın. Bunlar sahiplik anlamını aktarır.

Bir auto_ptr'ye döndüğünüzde, arayan kişiye hafızanın sahipliğini verdiğinizi söylüyorsunuz.

Shared_ptr döndürdüğünüzde, arayan kişiye ona bir referansınız olduğunu ve mülkiyetin bir parçası olduğunu söylüyorsunuz, ancak bu yalnızca onların sorumluluğunda değil.

Bu anlambilim, parametreler için de geçerlidir. Arayan kişi size bir auto_ptr iletirse, size sahiplik veriyor demektir.


1

Başkaları ilk başta bellek sızıntılarından kaçınmanın yollarından bahsetmişlerdir (akıllı işaretçiler gibi). Ancak bir profil oluşturma ve bellek analizi aracı, genellikle bellek sorunlarını bir kez elde ettiğinizde izlemenin tek yoludur.

Valgrind memcheck mükemmel bir ücretsiz olanıdır.


1

Yalnızca MSVC için, her .cpp dosyasının üstüne aşağıdakileri ekleyin:

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

Daha sonra, VS2003 veya üstü ile hata ayıklarken, programınızdan çıkıldığında herhangi bir sızıntı olduğu söylenecektir (yeni / silme işlemini izler). Basit ama geçmişte bana yardımcı oldu.


1

valgrind (yalnızca * nix platformları için geçerlidir) çok güzel bir bellek denetleyicidir


1

Belleğinizi manuel olarak yönetecekseniz, iki durumunuz vardır:

  1. Nesneyi yarattım (belki dolaylı olarak, yeni bir nesneyi tahsis eden bir işlevi çağırarak), onu kullanıyorum (veya onu kullandığını söylediğim bir işlev), sonra onu serbest bırakıyorum.
  2. Birisi bana referansı verdi, bu yüzden onu serbest bırakmamalıyım.

Bu kurallardan herhangi birini ihlal etmeniz gerekiyorsa, lütfen bunu belgeleyin.

Her şey işaretçi sahipliğiyle ilgili.


1
  • Nesneleri dinamik olarak ayırmaktan kaçının. Sınıfların uygun kurucuları ve yıkıcıları olduğu sürece, sınıf türünün bir değişkenini kullanın, ona bir işaretçi değil ve dinamik ayırmadan ve serbest bırakmadan kaçının çünkü derleyici bunu sizin için yapacaktır.
    Aslında bu aynı zamanda "akıllı işaretçiler" tarafından kullanılan ve diğer yazarların bazıları tarafından RAII olarak adlandırılan mekanizma ;-).
  • Nesneleri diğer işlevlere ilettiğinizde, işaretçiler yerine referans parametrelerini tercih edin. Bu, bazı olası hataları önler.
  • Mümkün olan yerlerde, özellikle nesnelere işaret eden parametreleri bildirin. Böylelikle nesneler "kazara" serbest bırakılamaz (const'ı atmanız dışında ;-))).
  • Programda bellek ayırma ve serbest bırakma yaptığınız yerlerin sayısını en aza indirin. Örneğin. aynı türü birkaç kez ayırır veya serbest bırakırsanız, bunun için bir işlev (veya bir fabrika yöntemi ;-)) yazın.
    Bu şekilde, gerekirse hata ayıklama çıktısını (hangi adreslerin ayrıldığı ve serbest bırakıldığı ...) kolayca oluşturabilirsiniz.
  • Birbiriyle ilişkili birkaç sınıfın nesnelerini tek bir işlevden ayırmak için bir fabrika işlevi kullanın.
  • Sınıflarınızın sanal bir yıkıcı ile ortak bir temel sınıfı varsa, hepsini aynı işlevi (veya statik yöntemi) kullanarak serbest bırakabilirsiniz.
  • Purify gibi araçlarla programınızı kontrol edin (maalesef çok $ / € / ...).

0

Bellek ayırma işlevlerini durdurabilir ve programdan çıkıldığında bazı bellek bölgelerinin olup olmadığını görebilirsiniz (ancak tüm uygulamalar için uygun değildir ).

Ayrıca yeni ve silme operatörleri ve diğer bellek ayırma işlevleri değiştirilerek derleme zamanında da yapılabilir.

Örneğin bu siteyi kontrol edin [C ++ 'da bellek ayırmada hata ayıklama] Not: Silme operatörü için bir numara da var, bunun gibi bir şey:

#define DEBUG_DELETE PrepareDelete(__LINE__,__FILE__); delete
#define delete DEBUG_DELETE

Bazı değişkenlerde dosyanın adını saklayabilirsiniz ve aşırı yüklenmiş silme operatörü hangi yerden çağrıldığını bilecektir. Bu şekilde, programınızdan her silme ve malloc'un izini sürebilirsiniz. Bellek kontrol dizisinin sonunda, hangi tahsis edilmiş bellek bloğunun 'silinmediğini' rapor edebilmelisiniz, bunu dosya adı ve satır numarasıyla tanımlamalısınız ki bu sizin ne istediğinizi tahmin ediyorum.

Ayrıca oldukça ilginç ve kullanımı kolay olan Visual Studio altında BoundsChecker gibi bir şey de deneyebilirsiniz .


0

Tüm ayırma işlevlerimizi, ön tarafa kısa bir dize ve sonuna bir nöbetçi bayrak ekleyen bir katmanla sarmalıyoruz. Örneğin, "myalloc (pszSomeString, iSize, iAlignment); veya new (" description ", iSize) MyObject () için bir çağrınız olur; bu, belirtilen boyut artı başlığınız ve sentineliniz için yeterli alanı dahili olarak ayırır. , hata ayıklama olmayan yapılar için bunu yorumlamayı unutmayın! Bunu yapmak için biraz daha fazla bellek gerekir, ancak faydaları maliyetlerden çok daha ağır basar.

Bunun üç faydası vardır - birincisi, belirli 'bölgelere' tahsis edilmiş kod için hızlı aramalar yaparak, ancak bu bölgelerin serbest bırakılması gerektiğinde temizlenmeden, hangi kodun sızdığını kolayca ve hızlı bir şekilde izlemenize olanak tanır. Tüm nöbetçilerin sağlam olduğundan emin olmak için kontrol edilerek bir sınırın üzerine ne zaman yazıldığını tespit etmek de yararlı olabilir. Bu, iyi gizlenmiş çökmeleri veya dizi yanlış adımlarını bulmaya çalışırken bizi defalarca kurtardı. Üçüncü fayda, büyük oyuncuların kim olduğunu görmek için bellek kullanımını izlemektir - örneğin, bir MemDump'taki belirli açıklamaların bir harmanlaması, 'sesin' beklediğinizden çok daha fazla yer kapladığını size söyler.


0

C ++, RAII göz önünde bulundurularak tasarlanmıştır. Bence C ++ 'da belleği yönetmenin daha iyi bir yolu yok. Ancak yerel kapsamda çok büyük parçaları (tampon nesneleri gibi) ayırmamaya dikkat edin. Yığın taşmalarına neden olabilir ve bu öbeği kullanırken sınır kontrolünde bir kusur varsa, diğer değişkenlerin veya dönüş adreslerinin üzerine yazabilirsiniz, bu da her türlü güvenlik açığına yol açar.


0

Farklı yerlerde ayırma ve yok etme ile ilgili tek örneklerden biri, iş parçacığı oluşturma (geçirdiğiniz parametre). Ancak bu durumda bile kolaydır. İşte bir iş parçacığı oluşturan işlev / yöntem:

struct myparams {
int x;
std::vector<double> z;
}

std::auto_ptr<myparams> param(new myparams(x, ...));
// Release the ownership in case thread creation is successfull
if (0 == pthread_create(&th, NULL, th_func, param.get()) param.release();
...

Bunun yerine iş parçacığı işlevi

extern "C" void* th_func(void* p) {
   try {
       std::auto_ptr<myparams> param((myparams*)p);
       ...
   } catch(...) {
   }
   return 0;
}

Oldukça kolay değil mi? İş parçacığı oluşturmanın başarısız olması durumunda kaynak auto_ptr tarafından serbest bırakılacak (silinecek), aksi takdirde sahiplik iş parçacığına geçecektir. Ya iş parçacığı o kadar hızlıysa, oluşturulduktan sonra kaynağı

param.release();

ana işlev / yöntemde çağrılır? Hiçbir şey değil! Çünkü auto_ptr'ye ayrılmayı yok saymasını 'söyleyeceğiz'. C ++ bellek yönetimi kolay değil mi? Alkış,

Ema!


0

Belleği, diğer kaynakları (tutamaçlar, dosyalar, db bağlantıları, soketler ...) yönettiğiniz şekilde yönetin. GC de size yardımcı olmaz.


-3

Herhangi bir işlevden tam olarak bir dönüş. Bu şekilde orada ayırma yapabilir ve asla kaçırmazsınız.

Aksi takdirde hata yapmak çok kolaydır:

new a()
if (Bad()) {delete a; return;}
new b()
if (Bad()) {delete a; delete b; return;}
... // etc.

Cevabınız buradaki örnek kodla eşleşmiyor mu? "Tek bir dönüş" cevabına katılıyorum ama örnek kod ne YAPILMAMASI gerektiğini gösteriyor.
simon

1
C ++ RAII'nin amacı, yazdığınız kod türünden tam olarak kaçınmaktır. C'de, bu muhtemelen yapılacak doğru şeydir. Ancak C ++ 'da kodunuz kusurludur. Örneğin: Yeni b () atarsa ​​ne olur? Bir.
paercebal
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.