Bellek yönetilmeyen programlamanın karmaşıklıkları nelerdir?


24

Başka bir deyişle, otomatik çöp toplama işlemi hangi belirli sorunları çözdü? Hiçbir zaman düşük seviyeli programlama yapmadım, bu yüzden kaynakları serbest bırakmanın ne kadar karmaşık olabileceğini bilmiyorum.

GC'nin ele aldığı hatalar (en azından bir dış gözlemciye), bir programcının dilini, kitaplıklarını, kavramlarını, deyimlerini vb. İyi tanıyan şeyler yapmaz. Ancak yanılıyor olabilirim: manuel hafıza kullanımı kendinden karmaşık mı?


3
Lütfen sorunuzun Garbace koleksiyonundaki Wikipedia makalesiyle ve özellikle de faydaları ile
yannis

Başka bir avantaj güvenliktir, örneğin arabellek taşması aşırı derecede kullanılabilir durumdadır ve diğer birçok güvenlik açığı bellek (yanlış) yönetiminden kaynaklanmaktadır.
StuperUser

7
@StuperUser: Bunun belleğin kökeni ile ilgisi yok. Bir GC'den gelen aşırı bellek hafızasını çok iyi kullanabilirsiniz. GC dillerinin bunu genellikle önlediği gerçeği ortogonaldir ve GC teknolojisinin otuz yıldan daha az süren dilleri, onları ayrıca arabellek taşma koruması sağlamak için karşılaştırıyorsunuz.
DeadMG

Yanıtlar:


29

Hiçbir zaman düşük seviye programlama yapmadım, bu yüzden kaynakları serbest bırakmanın ne kadar karmaşık olabileceğini bilmiyorum.

“Düşük seviye” tanımının zaman içinde nasıl değiştiği komik. Programlamayı ilk öğrendiğimde, basit bir tahsisat / serbest modeli mümkün kılan standartlaştırılmış bir yığın modeli sağlayan herhangi bir dilin gerçekten üst düzey olduğu kabul edildi. In düşük seviyeli programlama , bellek kendiniz takip etmek zorunda (değil tahsisleri, ancak bellek konumları kendileri!), Ya da gerçekten fantezi duygu olsaydı kendi yığın allocator yazmak istiyorum.

Bunu söyledikten sonra, bu konuda gerçekten korkutucu veya "karmaşık" bir şey yok. Çocukken ve annen, onlarla oynamayı bitirdiğinde oyuncaklarını atmanı, senin hizmetçinin olmadığını ve senin odanı temizlemeyeceğini söylediğini hatırlıyor musun? Hafıza yönetimi sadece koda uygulanan aynı prensiptir. (GC bir hizmetçi olması gibi olacak sonra temizlemek, ama çok tembel ve biraz clueless bu.) Onun prensibi basittir: Kodunuzdaki Her değişken bir ve sadece bir sahibi vardır ve o sahibinin sorumluluğunda Artık ihtiyaç duyulmadığında değişken hafızasını boşaltın. ( Tek Sahiplik Prensibi) Bu, tahsis başına bir çağrı gerektirir ve mülkiyeti ve temizliği bir şekilde veya başka bir şekilde otomatikleştiren birkaç şema vardır, bu çağrıyı kendi kodunuza bile yazmanıza gerek kalmaz.

Çöp toplamanın iki sorunu çözmesi gerekiyordu. İstisnasız bir şekilde bunlardan birinde çok kötü bir iş çıkarır ve uygulamaya bağlı olarak diğerinde de olabilir veya olmayabilir. Sorunlar hafıza sızıntısı (işiniz bittikten sonra hafızada kalmanız) ve sarkan referanslar (işiniz bitmeden hafızayı boşaltmaktır.) Her iki konuya da bakalım:

Sarkan referanslar: İlk önce bunu tartışmak, çünkü gerçekten ciddi olanı. Aynı nesneye iki işaretçi var. Birini serbest bırakıyorsun, diğerini farketmiyorsun. Sonra bir sonraki noktada, ikincisini okumaya (veya yazmaya ya da serbest bırakmaya) çalışırsınız. Tanımlanmamış davranışlar ortaya çıkar. Bunu farketmezseniz, hafızanızı kolayca bozabilirsiniz. Çöp toplama işleminin, tüm referansları bulana kadar hiçbir şeyin serbest kalmamasını sağlayarak imkansız hale getirmesi beklenir. Tamamen yönetilen bir dilde, harici, yönetilmeyen bellek kaynakları ile uğraşmanız gerekene kadar bu neredeyse işe yarar. Sonra tekrar kare 1'e dönüyor. Ve yönetilmeyen bir dilde, işler hala daha zorlu. (Mozilla'da etrafta dolaş

Neyse ki, bu konuyla başa çıkmak temelde çözülmüş bir sorundur. Çöp toplayıcıya ihtiyacınız yok, hata ayıklama hafıza yöneticisine ihtiyacınız var . Örneğin, Delphi'yi kullanıyorum ve tek bir harici kütüphane ve basit bir derleyici yönergesi ile ayırıcıyı "Tam Hata Ayıklama Modu" olarak ayarlayabilirim. Bu, kullanılmış belleği takip eden bazı özelliklerin etkinleştirilmesine karşılık olarak genel gider olarak göz ardı edilebilir (% 5'ten az) performans ekler. Eğer bir nesneyi serbest bırakırsam, hafızası0x80bayt (hata ayıklayıcıda kolayca algılanabilir) ve serbest bırakılmış bir nesnede sanal bir yöntem (yıkıcı dahil) çağırmayı denersem, programı üç yığın izli bir hata kutusu ile fark eder ve keser - nesne oluşturulduğunda, Ne zaman serbest kaldı ve şimdi olduğum yerde - artı bazı yararlı bilgiler, sonra bir istisna ortaya çıkarır. Bu açıkça sürüm oluşturmalar için uygun değildir, ancak sarkan referans sorunlarının izlenmesini ve düzeltilmesini önemsiz kılar.

İkinci sorun ise bellek sızıntısı. Artık ihtiyaç duymadığınızda, ayrılmış belleği tutmaya devam ettiğinizde olan budur. Çöp toplama ile veya çöp toplama olmadan herhangi bir dilde olabilir ve yalnızca kodunuzu doğru yazarak düzeltilebilir. Çöp toplama, belirli bir bellek sızıntısı biçimini azaltmaya yardımcı olur; bu, henüz serbest bırakılmamış bir belleğe geçerli bir referansınız olmadığında gerçekleşen türdür; bu, program sona erene kadar belleğin ayrılmış kalması anlamına gelir. Ne yazık ki, bunu otomatik olarak gerçekleştirmenin tek yolu her tahsisi bir bellek sızıntısına dönüştürmektir!

Eğer böyle bir şey söylemeye çalışırsam muhtemelen GC taraftarları tarafından kandırılacağım, bu yüzden açıklamama izin ver. Bir bellek sızıntısı tanımının, artık ihtiyacınız olmadığında ayrılmış bellek için tutulduğunu unutmayın. Bir şeye referans alamamaya ek olarak, aynı zamanda gereksiz bir referansa sahip olarak, onu serbest bırakmanız gerektiğinde bir konteyner nesnesinde tutmak gibi, bellek sızdırması yapabilirsiniz. Bunu yaparak bazı bellek sızıntıları gördüm ve bir GC'niz olup olmadığını anlamak çok zor, çünkü belleğe tamamen geçerli bir referans içeriyorlar ve hata ayıklama araçları için net bir "hata" yok. yakalamak. Bildiğim kadarıyla, bu tür bellek sızıntısını yakalamanıza izin veren otomatik bir araç yok.

Bu yüzden bir çöp toplayıcı sadece referans olmayan çeşitli bellek sızıntıları ile ilgilidir, çünkü otomatik bir şekilde ele alınabilecek tek tip budur. Tüm referanslarınızı her şeye izleyebilir ve her nesneyi kendisine işaret eden sıfır referansı olduğu anda serbest bırakabilirse, en azından referanssızlık problemi açısından mükemmel olurdu. Bunu otomatik bir şekilde yapmak referans sayma olarak adlandırılır ve bazı sınırlı durumlarda yapılabilir, ancak bununla ilgilenmesi gereken kendi sorunları vardır. (Örneğin, A nesnesine bir referans tutan B nesnesine bir başvuru tutan A nesnesi, bir başvuru sayım şemasında, A veya B dış referansı olmasa bile hiçbir nesne otomatik olarak serbest bırakılamaz.) çöp toplayıcıları izlemeyi kullanıryerine: Başlangıç bilinen iyi nesne kümeleri ile, hepsi nesneleri bulmak, referans aldıkları tüm nesneleri bulmak onlar başvurmak ve bu yüzden her şeyi buldum yinelemeli kadar üzerinde. İzleme sürecinde bulunmayan şey çöptür ve atılabilir. (Bunu başarılı bir şekilde yapmak elbette, izleme çöp toplayıcısının bir başvuru ile bir işaretçiye benzeyen rastgele bir bellek parçası arasındaki farkı her zaman anlatabildiğinden emin olmak için tür sistemine belirli kısıtlamalar getiren bir yönetilen dili gerektirir.)

İzlemenin iki sorunu var. Birincisi, yavaştır ve program devam ederken, yarış koşullarından kaçınmak için programın az çok duraklatılması gerekir. Bu, programın bir kullanıcı ile etkileşime girmesi gerektiğinde veya bir sunucu uygulamasında düşük performans göstermesi durumunda dikkat çekici yürütmeye neden olabilir. Bu, tahsis edilen hafızayı “nesiller” e ayırma gibi çeşitli tekniklerle azaltılabilir, eğer tahsis ilk defa denenmemişse, bir süre daha takılmanın muhtemel olduğu ilkesiyle. Hem .NET çerçevesi hem de JVM, kuşak çöp toplayıcıları kullanır.

Ne yazık ki, bu ikinci sorunla besleniyor: işin bitince bellek boşalmıyor. Bir nesneyle işiniz bittikten hemen sonra izleme hemen gerçekleşmezse, bir sonraki ize kadar yapışacak, hatta ilk nesile geçerse daha uzun sürecektir. Aslında, gördüğüm .NET çöp toplayıcısının en iyi açıklamalarından biri, süreci olabildiğince hızlı yapmak için GC'nin toplayabildiği kadar ertelemesi gerektiğini açıklıyor! Bu yüzden hafıza sızıntıları sorunu mümkün olduğunca tuhaf bir şekilde mümkün olduğunca uzun süredir hafızaya sızarak "çözüldü" ! Bir GC'nin her tahsisi bir bellek sızıntısına çevirdiğini söylediğimde demek istediğim bu Aslında, herhangi bir nesne olacak garantisi yoktur hiç toplanabilir.

Neden bu sorun, ihtiyaç duyulduğunda hafızanın hala geri kazanılması durumunda mı? Birkaç nedenden dolayı. İlk olarak, önemli miktarda bellek alan büyük bir nesneyi (örneğin bir bitmap) ayırdığınızı hayal edin. Ve sonra işiniz bittikten kısa bir süre sonra, aynı (veya aynı miktarda) belleği alan başka bir büyük nesneye ihtiyacınız var. İlk nesne serbest bırakıldıysa, ikincisi hafızasını tekrar kullanabilir. Ancak, çöp toplanan bir sistemde, sonraki izin çalışmasını hala bekliyor olabilirsiniz ve bu nedenle ikinci bir büyük nesne için gereksiz yere boşa harcıyorsunuz. Temel olarak bir yarış durumu.

İkincisi, belleği, özellikle büyük miktarlarda gereksiz yere tutmak, modern bir çoklu görev sisteminde sorunlara neden olabilir. Çok fazla fiziksel bellek kullanırsanız, programınızın veya diğer programların sayfalarını (belleklerinin bir kısmını diske çevirerek) taramasına neden olabilir ve bu da işleri yavaşlatır. Sunucular gibi belirli sistemler için disk belleği yalnızca sistemi yavaşlatmakla kalmaz, yük altındaysa her şeyi çökertebilir.

Sarkan referanslar problemi gibi, referanssızlık problemi de bir hata ayıklama hafızası yöneticisi ile çözülebilir. Yine, Delphi'nin FastMM bellek yöneticisinden Tam Hata Ayıklama Modundan bahsedeceğim, çünkü en çok tanıdığım kişi. (Diğer diller için benzer sistemler olduğundan eminim)

FastMM altında çalışan bir program sona erdiğinde, isteğe bağlı olarak, hiçbir zaman serbest bırakılmayan tüm ayırmaların varlığını rapor etmesini sağlayabilirsiniz. Tam Hata Ayıklama Modu bir adım daha ileri götürür: dosyayı yalnızca ayırma türünü değil aynı zamanda ayrıldığı zamanki yığın izini ve sızan her ayırma için diğer hata ayıklama bilgilerini içeren bir diske kaydedebilir. Bu, referanssız bellek sızıntısının önemsiz şekilde izlenmesini sağlar.

Buna gerçekten baktığınızda, çöp toplama sarkan referansları önlemekle iyi olabilir veya olmayabilir, ve evrensel olarak bellek sızıntılarını ele alma konusunda kötü bir iş çıkarır. Aslında onun bir erdem çöp toplama değil, yan etkisidir: yığın sıkıştırma işlemini gerçekleştirmek için otomatik bir yol sağlar. Bu, sürekli olarak uzun süre çalışan ve yüksek derecede bellek kaybı olan programları öldüren bir yayılma problemini (yığın parçalanması yoluyla bellek tükenmesi) önleyebilir ve yığın sıkıştırması, çöp toplama işlemi olmadan neredeyse imkansızdır. Bununla birlikte, bugünlerde herhangi bir iyi bellek tahsisatçısı, parçalanmayı en aza indirmek için kovalar kullanmaktadır, bu da parçalanmanın sadece aşırı koşullarda bir sorun olacağı anlamına gelmektedir. Yığın parçalanmasının bir sorun olabileceği bir program için, Sıkıştırıcı bir çöp toplayıcı kullanılması önerilir. Ancak IMO, başka bir durumda, çöp toplamanın kullanımı, erken optimizasyondur ve “çözdüğü” sorunlara daha iyi çözümler sunar.


5
Bu cevabı seviyorum - her zaman ve daha sonra okumaya devam ediyorum. İlgili bir açıklama bulamadım, söyleyebileceğim tek şey - teşekkür ederim.
vemv

3
Evet, GC'lerin hafızayı "sızdırma" eğiliminde olduğunu (en azından bir süre) belirtmek isterim, ancak bu bir sorun değildir çünkü bellek ayırıcı toplamadan önce bellek ayıramadığında belleği toplayacaktır. GC olmayan bir dille, bir sızıntı her zaman bir sızıntı olarak kalır, yani çok fazla toplanmamış bellek nedeniyle bellekte bitebilir. "çöp toplama işlemi erken optimizasyondur" ... GC bir optimizasyon değildir ve bu konuda düşünülmemiştir. Aksi takdirde, iyi cevap.
Thomas Eding

7
@ThomasEding: GC kesinlikle bir optimizasyondur; Performans pahasına ve diğer çeşitli program kalite ölçümleri pahasına minimum programcı çabası için optimize eder .
Mason Wheeler

5
Mozilla'nın böcek izleyicisine bir noktada işaret etmeniz komik, çünkü Mozilla oldukça farklı bir sonuca varmış. Firefox, bellek yönetimi hatalarından kaynaklanan sayısız güvenlik sorununa sahipti ve olmaya devam ediyor. Bunun, tespit edildikten sonra hatayı düzeltmenin ne kadar kolay olduğu ile ilgili olmadığını unutmayın; genellikle hasar zaten geliştiricilerin konuyla ilgili farkına varıncaya kadar yapılır. Mozilla, bu tür hataların ilk başta ortaya çıkmasını önlemeye yardımcı olmak için Rust programlama dilini tam olarak finanse ediyor.

1
Rust, çöp toplama özelliğini kullanmaz, çalışma zamanında hataları tespit etmek için bir hata ayıklayıcı kullanmak yerine sadece derleme zamanı kontrolleriyle masonun tanımladığı gibi referans saymayı kullanır ...
Sean Burton

13

Çöp toplanmamış bir bellek yönetimi tekniğini, C ++ 'ın RAII gibi mevcut popüler sistemlerde kullanılan çöp toplayıcıları olarak eşdeğer bir çağdan ele alalım. Bu yaklaşım göz önüne alındığında, o zaman otomatik çöp toplama kullanmamanın maliyeti azdır ve GC, kendi problemlerinin çoğunu ortaya çıkarmaktadır. Bu nedenle, "Çok değil" sorununuzun cevabıdır.

Unutmayın, insanlar GC olmadığını düşündüğünde, düşünürler mallocve free. Fakat bu devasa bir mantık yanlışıdır - 1970'lerin başındaki GC olmayan kaynak yönetimini 90'ların sonundaki çöp toplayıcılarıyla karşılaştıracaksınız. Bu açıkçası oldukça haksız bir karşılaştırma - doğru bir şekilde hatırlıyorsam, herhangi bir anlamlı programı çalıştırmak için ne zaman mallocve ne zaman kullanılmakta olan çöp toplayıcıları freeçok yavaştı. Bir şeyi belli belirsiz eşdeğer bir zaman diliminde karşılaştırmak, örneğin unique_ptr, çok daha anlamlıdır.

Çöp toplayıcıları, oldukça nadir deneyimler olmasına rağmen, referans çevrimlerini daha kolay ele alabilirler. Ek olarak, GC'ler sadece "kusabilir" çünkü GC, tüm hafıza yönetimini üstlenir, bu da daha hızlı cihaz geliştirmelerine yol açabilecekleri anlamına gelir.

Öte yandan, kendi GC havuzları dışında herhangi bir yerden gelen hafıza ile uğraşırken büyük problemlerle karşılaşırlar. Ek olarak, eşzamanlılık söz konusu olduğunda faydalarını çok kaybederler, çünkü yine de nesne sahipliğini göz önünde bulundurmanız gerekir.

Düzenleme: Bahsettiğiniz şeylerin çoğu GC ile ilgisi yoktur. Bellek yönetimini ve nesne yönelimini karıştırıyorsunuz. Bakın, işte size bir şey: Tamamen yönetilmeyen bir sistemde, C ++ gibi programlarsanız, istediğiniz kadar sınır kontrolünüz olabilir ve Standart konteyner sınıfları bunu sunar. Sınır kontrolü, örneğin ya da güçlü yazma hakkında GC hakkında hiçbir şey yok.

Bahsettiğiniz problemler GC değil nesne yönelimi ile çözülür. Dizi belleğinin orijini ve dışına yazmadığınızdan emin olmak ortogonal kavramlardır.

Düzenleme: Daha ileri tekniklerin herhangi bir dinamik bellek ayırma biçimine olan gereksinimi önleyebileceğini belirtmekte fayda vardır. Örneğin, kullanımı dikkate bu da hiç dinamik ayırma C ++ Y-kombinasyonunu uygular.


Buradaki genişletilmiş tartışma temizlendi: eğer herkes konuyu daha fazla tartışmak için sohbete götürebilirse , gerçekten takdir ediyorum.

@DeadMG, birleştiricinin ne yapması gerektiğini biliyor musunuz? COMBINE olması gerekiyordu. Tanım olarak, birleştirici, serbest değişkenleri olmayan bir fonksiyondur.
SK-mantığı

2
@ SK-mantık: Tamamen şablonla uygulamayı seçebilirdim ve hiçbir üye değişkenine sahip olamazdım. Ancak, kapanışlara geçemezsiniz, ki bu da kullanışlılığını önemli ölçüde sınırlar. Sohbete gelmek ister misin?
DeadMG

@DeadMG, bir tanım kristal açıktır. Serbest değişken yok. Y-birleştiriciyi tanımlamak mümkünse herhangi bir dili "yeterince işlevsel" olarak görüyorum (sizin yönteminizle değil). Büyük bir "+", S, K ve I birleştiricileriyle tanımlamak mümkün ise. Aksi halde dil yeterince anlamlı değildir.
SK-mantığı

4
@ SK-mantık: Neden sohbete gelmiyorsun , moderatörün istediği gibi? Ayrıca, bir Y-birleştiricisi bir Y-birleştiricisidir, işi yapar veya yapmaz. Y-birleştiricinin Haskell versiyonu temelde bununla tamamen aynı, sadece ifade edilen durumun sizden saklı olduğu.
DeadMG

11

Çöp toplayan dillerin sağladığı "kaynakları serbest bırakma konusunda endişelenme özgürlüğü", büyük ölçüde bir yanılsamadır. Hiçbir şeyi kaldırmadan bir haritaya bir şeyler eklemeye devam edin, yakında ne dediğimi anlayacaksınız.

Aslında, GCed dillerinde yazılmış programlarda bellek sızıntıları oldukça sıktır, çünkü bu diller programcıları tembel kılmaya meyillidir ve dilin her zaman bir şekilde (sihirli bir şekilde) her nesneye özen gösterecekleri yanlış bir güvenlik hissi kazanmalarını sağlar. artık düşünmek istemiyorum.

Çöp toplama basitçe başka, daha asil bir amacı olan diller için gerekli bir tesistir: her şeyi bir nesneye işaretçi olarak ele almak ve aynı zamanda programcının, bir işaretçi olduğu gerçeğini gizlemek; işaretçi aritmetik ve benzeri girişimleriyle intihar. Nesne olan her şey, GCed dillerinin, GCed dışındaki dilden çok daha fazla nesne tahsis etmesi gerektiği anlamına gelir; bu, eğer bu nesneleri programlayıcıya bırakma yükünü yüklerlerse, son derece çekici olmazlar.

Ayrıca, çöp toplama programı, programcıya, her birinin tahsis edilmesini sağlamak için ifadeleri ayrı ifadelere ayırmak zorunda kalmadan, ifadeleri ayrı ifadelere bölmek zorunda kalmadan, işlevsel bir programlama biçiminde sıkı kod yazma becerisi kazandırmak için kullanışlıdır. ifadeye katılan tek nesne.

Tüm bunların yanı sıra, cevabımın başında " önemli ölçüde bir yanılsama olduğunu" yazdığımı lütfen unutmayın . Ben yazmadım öyle bir yanılsama. Bunun çoğunlukla bir illüzyon olduğunu bile yazmadım . Çöp toplama, programcıdan, nesnelerinin serbest bırakılmasına katılmaktan vazgeçme görevini elinden almakta faydalıdır. Dolayısıyla bu anlamda bir verimlilik özelliğidir.


4

Çöp toplayıcı herhangi bir "hatayı" ele almaz. Bazı yüksek seviye dilleri anlambiliminin zorunlu bir parçasıdır. Bir GC ile, lexical kapanmalar ve benzerleri gibi daha yüksek soyutlama seviyelerini tanımlamak mümkündür, oysa manuel bir hafıza yönetimi ile bu soyutlamalar sızıntı yapacaktır, gereksiz yere kaynak yönetiminin düşük seviyelerine bağlanmaktadır.

Yorumlarda belirtilen "tek sahiplik ilkesi", böyle sızdıran bir soyutlamaya oldukça iyi bir örnektir. Bir geliştirici, herhangi bir temel temel veri yapısı örneğine olan bağlantıların sayısı hakkında hiçbir zaman endişelenmemelidir, aksi halde herhangi bir kod parçası, çok sayıda ek (kodun kendisinde doğrudan görünmez) sınırlamaları ve gereklilikleri olmadan genel ve şeffaf olamaz . Bu tür bir kod, sorumluluk ayırma ilkesi katmanlarının dayanılmaz bir ihlali olan (yazılım mühendisliğinin temel bir yapı taşıdır, ne yazık ki, çoğu düşük seviye geliştiricisinin çoğu tarafından saygı gösterilmediği) bir kod olarak oluşturulamaz.


1
@Mason Wheeler, hatta C ++ çok sınırlı bir kapatma şekli uygulamaktadır. Ancak, neredeyse uygun, genellikle kullanışlı bir kapatma değildir.
SK-mantığı

1
Yanılıyorsun. Hiçbir GC, yığın değişkenlerine başvuramayacağınız gerçeğinden sizi koruyamaz. Komik olan C ++ 'da "İşaretçiyi dinamik olarak tahsis edilmiş bir değişkene uygun ve otomatik olarak imha edilecek bir kopyaya kopyala" yaklaşımını da alabilirsiniz.
DeadMG

1
@DeadMG, kodunuzun üstüne kurduğunuz herhangi bir seviye boyunca düşük seviyeli varlıklardan sızdığını görmüyor musunuz?
SK-mantığı

1
@ SK-Logic: Tamam, bir terminoloji problemimiz var. "Gerçek kapatma" tanımınız nedir ve Delphi'nin kapanması için ne yapabilirler? (Ve tanımınıza bellek yönetimi ile ilgili herhangi bir şeyi dahil etmek, kale direklerini hareket ettirir. Uygulama detayları hakkında değil davranış hakkında konuşalım.)
Mason Wheeler

1
@ SK-Logic: ... ve Delphi'nin kapanışlarının gerçekleştiremediği basit tipsiz lambda kapaklarıyla yapılabilecek bir örnek var mı?
Mason Wheeler

2

Gerçekten, kendi hafızanızı idare etmek sadece bir tane daha potansiyel hata kaynağıdır.

Kullandığınız bir aramayı unutursanız free(veya hangi dilde kullanıyorsanız kullanın), programınız tüm testlerini geçebilir, ancak bellek sızdırabilir. Ve orta derecede karmaşık bir programda, bir çağrıyı gözden kaçırmak oldukça kolaydır free.


3
Cevapsız freeen kötü şey değil. Erken freeçok daha yıkıcı.
herby,

2
Ve çift free!
quant_dev

Hehe! Yukarıdaki iki yorum ile birlikte giderdim. Bu girişimlerden birini kendim asla yapmadım (bildiğim kadarıyla), ancak etkilerin ne kadar korkunç olabileceğini görebiliyorum. Quant_dev'in cevabı her şeyi söylüyor - hafıza ayırma ve tahsisat ayırma hatalarını bulmak ve düzeltmek çok zor.
Dawood Monica

1
Bu bir yanlışlık. "1970'lerin başını" ile "1990ların sonlarını" karşılaştırıyorsunuz. O sırada var olan mallocve freegitmek için GC olmayan yol olan GC'ler, herhangi bir şey için faydalı olmak için fazlasıyla yavaştı. RAII gibi modern bir GC olmayan yaklaşımla karşılaştırmanız gerekiyor.
DeadMG

2
@DeadMG RAII manuel bellek yönetimi değil
quant_dev

2

Manuel kaynak sadece sıkıcı değil, aynı zamanda hata ayıklamak da zor. Başka bir deyişle, yalnızca doğru yapmak sıkıcı değil, aynı zamanda yanlış yaptığınız zaman sorunun nerede olduğu da açık değildir. Bunun nedeni, örneğin sıfıra bölmenin aksine, hatanın etkilerinin hata kaynağından ortaya çıkması ve noktaların bağlanması zaman, dikkat ve tecrübe gerektirmesidir.


1

Çöp toplama işleminin, büyük bir ilerleme dalgasının parçası olmaktan başka, GC ile ilgisi olmayan dil gelişimi için çok fazla kredi kazandığını düşünüyorum.

GC'ye tanıdığım tek sağlam yarar, programınızdaki bir nesneyi serbest bırakıp, herkesin işini bitirdiğinde onun biteceğini bilmenizdir. Başka bir sınıfın yöntemine geçirebilir ve endişelenmeyin. Başka hangi yöntemlere geçildiği veya başka hangi sınıfların ona referans verdiği ile ilgilenmiyorsunuz. (Bellek sızıntıları, onu oluşturan sınıfı değil, bir nesneyi referans alan sınıfın sorumluluğundadır.)

GC olmadan, tahsis edilen hafızanın tüm yaşam döngüsünü takip etmeniz gerekir. Bir adresi, onu oluşturan alt rutinden yukarı veya aşağı ilettiğinizde, bu belleğe kontrolden çıkmış bir referansınız olur. Kötü eski günlerde, yalnızca bir iş parçacığı olsa bile, özyineleme ve genel bir işletim sistemi (Windows NT), ayrılan belleğe erişimi kontrol etmemi imkansız hale getirdi. Aletin zorunda serbest bütün referanslar dışarı temizlenir got kadar süre için yaklaşık bellek bloklarını tutmak için kendi tahsisat sisteminde yöntemi. Bekleme süresi saf bir tahminde bulundu, ama işe yaradı.

Yani bildiğim tek GC yardımı bu, ancak onsuz yaşayamam. Herhangi bir OOP'un onsuz uçacağını sanmıyorum.


1
Kafamın hemen üstünde, Delphi ve C ++, GC olmadan OOP dilleri olarak oldukça başarılı oldular. "Kontrolden çıkma referansları" nı önlemeniz gereken tek şey biraz disiplin. Tek Mülkiyet İlkesini anlarsanız (cevabımı görün), burada bahsettiğiniz sorunlar tamamen sorun olmaz.
Mason Wheeler

@MasonWheeler: Sahip nesnenin serbest bırakılma zamanı geldiğinde, kendisine ait nesnelerin referans aldığı tüm yerleri bilmesi gerekir. Bu bilgiyi korumak ve referansları kaldırmak için kullanmak bana çok fazla iş gibi geliyor. Sıklıkla referansların henüz temizlenemediğini öğrendim. Sahibini silinmiş olarak işaretlemek zorunda kaldım, sonra kendini güvenli bir şekilde serbest bırakıp bırakamayacağını görmek için periyodik olarak hayata geçirdim. Delphi'yi hiç kullanmadım, ancak yürütme verimliliğindeki küçük bir fedakarlık için C # / Java, geliştirme süresinde C ++ 'a büyük bir destek verdi. (GC nedeniyle hepsi değil, ama yardımcı oldu.)
RalphChapin

1

Fiziksel Sızıntı

GC'nin ele aldığı hatalar (en azından bir dış gözlemciye), bir programcının dilini, kitaplıklarını, kavramlarını, deyimlerini vb. İyi tanıyan şeyler yapmaz. Ancak yanılıyor olabilirim: manuel hafıza kullanımı kendinden karmaşık mı?

Bellek yönetimini el ile mümkün kılan ve mümkün olduğunca telaffuz edilen C ucundan geliyoruz, böylece aşırı uçları karşılaştırıyoruz (C ++ çoğunlukla GC olmadan bellek yönetimini otomatikleştiriyor), GC'yi karşılaştırırken "gerçekten değil" derim. sızıntılara geliyor . Yeni başlayanlar ve bazen bir profesyonel bile freeverilenler için yazmayı unutabilir malloc. Kesinlikle oldu.

Ancak, valgrindkaçak tespiti gibi , kodun uygulanmasında, bu tür hataların tam kod satırına kadar / nerede meydana geldiği zaman hemen farkedilecek araçlar vardır . Bu CI'ye entegre edildiğinde, bu tür hataları birleştirmek neredeyse imkansız hale gelir ve bunları düzeltmek için kolay bir pasta olur. Bu yüzden hiçbir zaman makul bir standartta herhangi bir ekip / süreçte önemli bir şey değil.

Kabul edilirse, freeçağrılmadığı yerlerde test radarı altında uçan , belki de bozuk bir dosya gibi gizli bir harici giriş hatasıyla karşılaşıldığında, belki de sistem 32 bayt veya bir şey sızıntısı olduğu bazı egzotik uygulama durumları olabilir . Bunun kesinlikle iyi test standartları ve sızıntı tespit araçları altında bile gerçekleşebileceğini düşünüyorum, ancak neredeyse hiç gerçekleşmeyen bir şeyin üzerine biraz bellek sızdırmak da çok da önemli olmayacak. GC'nin önleyemeyeceği şekilde, genel uygulama yollarında bile büyük kaynakları sızdırabileceğimiz daha büyük bir sorun göreceğiz.

Ayrıca, bir nesnenin kullanım ömrünün bir tür ertelenmiş / asenkron işlem, belki başka bir iş parçacığı için uzatılması gerektiğinde, GC'nin takma biçimine benzeyen bir şey olmadan da zordur.

Sarkan Pointer

Daha fazla manuel bellek yönetimi türüyle asıl sorun bana sızıntı değil. C veya C ++ dilinde yazılan kaç tane yerel uygulama olduğunu biliyoruz. Linux çekirdeği sızdırıyor mu? MySQL? CryEngine 3? Dijital ses iş istasyonları ve birleştiriciler? Java VM sızdırıyor mu (yerel kodda uygulanıyor)? Photoshop?

Herhangi bir şey varsa, etrafa baktığımızda en sızan uygulamalar GC şemaları kullanılarak yazılmış olma eğilimindedir. Ancak, çöp toplama işleminde bir çarpma olarak ele alınmadan önce, yerel kodun bellek sızıntılarıyla hiç ilgisi olmayan önemli bir sorunu var.

Benim için mesele her zaman güvenlik oldu. Hatta zaman freehafıza bir işaretçi üzerinden, kaynağa başka işaretçiler varsa, bunlar (geçersiz kılınan) işaretçileri sarkan hale gelecektir.

Bu sarkan işaretçilerin pointe'lerine erişmeye çalıştığımızda, neredeyse her zaman zor ve acil bir çökmeye neden olan bir segfault / erişim ihlali olmasına rağmen tanımsız davranışlarla karşılaşıyoruz.

Yukarıda listelediğim tüm bu yerel uygulamalar, potansiyel olarak, bu sorun nedeniyle öncelikle bir çökmeye yol açabilecek son derece gizli bir davaya sahiptir ve kesinlikle yerel kodda yazılmış çok sık sık ağır olan ve oldukça sık sık kullanılan kaliteli uygulamaların adil bir payı vardır. bu sorun nedeniyle büyük oranda.

... ve GC kullanıp kullanmadığınızdan bağımsız olarak kaynak yönetimi zordur. Pratik fark, kaynak yanlış yönetimine yol açan bir hata karşısında genellikle sızıntı (GC) veya çökme (GC olmadan) şeklindedir.

Kaynak Yönetimi: Çöp Toplama

Karmaşık kaynak yönetimi ne olursa olsun zor, manuel bir işlemdir. GC burada hiçbir şeyi otomatik hale getiremez.

Bu nesneye sahip olduğumuz örneğe bakalım, "Joe". Joe'ya üye olduğu birkaç kuruluş tarafından başvuruda bulunulur. Her ay ya da öylesine onlar kredi kartından bir üyelik ücreti almak.

görüntü tanımını buraya girin

Ayrıca ömrünü kontrol etmek için Joe'ya bir referansımız var. Diyelim ki, programcılar olarak artık Joe'ya ihtiyacımız yok. Bizi sinirlendirmeye başladı ve artık onunla uğraşarak zamanlarını boşa harcadığı bu örgütlere ihtiyacımız yok. Bu yüzden yaşam çizgisi referansını kaldırarak onu yeryüzünden silmeye çalışıyoruz.

görüntü tanımını buraya girin

... ama bekle, çöp toplama kullanıyoruz. Joe'ya yapılan her güçlü referans onu etrafta tutacak. Bu yüzden kendisine ait olduğu kuruluşlardan referansları kaldırıyoruz (aboneliği iptal ediyor).

görüntü tanımını buraya girin

... boğmacalar dışında, dergi aboneliğini iptal etmeyi unuttuk! Artık Joe hafızada kalıyor, bizi rahatsız ediyor ve kaynakları kullanıyor ve dergi şirketi de her ay Joe üyeliğini işlemeye devam ediyor.

Bu, çöp toplama şemaları kullanılarak yazılmış birçok karmaşık programın, sızıntı yapıp daha fazla bellek kullandıkça daha fazla bellek kullanmaya ve muhtemelen daha fazla işlemeye (tekrar eden dergi aboneliği) neden olmasına neden olabilecek ana hatadır. Bu referanslardan bir veya daha fazlasını kaldırmayı unuttular, böylece çöp toplayıcının sihirini tüm program kapanana kadar yapmasını imkansız hale getirdiler.

Ancak program çökmedi. Tamamen güvenli. Sadece hafızayı karıştırmaya devam edecek ve Joe hala oyalanacak. Pek çok uygulama için, konuya giderek daha fazla bellek / işlem yaptığımız bu tür sızdıran davranışlar, özellikle günümüzde makinelerimizde ne kadar bellek ve işlem gücü olduğu göz önüne alındığında, zorlu bir çöküşte çok tercih edilebilir.

Kaynak Yönetimi: Manuel

Şimdi Joe'ya işaretçiler ve manuel bellek yönetimini kullandığımız alternatifleri düşünelim:

görüntü tanımını buraya girin

Bu mavi bağlantılar Joe'nun ömrünü yönetmiyor. Onu yeryüzünün suratından çıkarmak istiyorsak, onu elle imha etmek istiyoruz, şöyle:

görüntü tanımını buraya girin

Şimdi bu normalde bizi her yere sarkan işaretçilerle bırakacaktı, o yüzden işaretçileri Joe'ya kaldıralım.

görüntü tanımını buraya girin

... yine aynı hatayı tekrar yaptık ve Joe'nun dergi aboneliğini iptal etmeyi unuttuk!

Şimdi hariç, sarkan bir işaretçi var. Dergi aboneliği Joe’nun aylık ücretini işleme koymaya çalıştığında, tüm dünya patlayacak - genellikle hemen sert bir çarpışma yaşanır.

Geliştiricinin bir kaynağa tüm işaretçileri / referansları el ile kaldırmayı unuttuğu aynı temel kaynak yanlış yönetimi hatası, yerel uygulamalarda birçok çökmeye neden olabilir. Normalde daha uzun süre çalıştıkları için hafızayı korumazlar, çünkü bu durumda genellikle düpedüz çarpışma olur.

Gerçek dünya

Şimdi yukarıdaki örnek gülünç basit bir diyagram kullanıyor. Bir gerçek dünya uygulaması, tam bir grafiği kapsayacak şekilde bir araya getirilmiş binlerce görüntü, bir sahne grafiğinde saklanan yüzlerce farklı türde kaynak, bazılarıyla ilişkili GPU kaynakları, diğerlerine bağlı hızlandırıcılar, yüzlerce eklentiye dağılmış gözlemciler gerektirebilir sahnede değişiklikler, gözlemciler gözlemleyen gözlemciler, animasyonlar ile senkronize edilmiş sesler, vb. gibi çeşitli varlık türlerini izlemek. Yukarıda tarif ettiğim yanlışlıktan kaçınmak kolay gibi görünebilir, ancak gerçek dünyada bu kadar basit değil Milyonlarca kod satırı kapsayan karmaşık bir uygulama için üretim kod temeli.

Bir kişinin, bir gün, bu kod tabanındaki kaynakları yanlış yönetme olasılığı oldukça yüksektir ve bu olasılık GC ile veya GC olmadan aynıdır. Asıl fark, bu hatanın sonucu olarak ne olacağını ve bu hatanın ne kadar çabuk tespit edildiğini ve düzeltileceğini de potansiyel olarak etkiler.

Kaçak vs Çarpışma

Şimdi hangisi daha kötü? Anında bir kaza ya da Joe'nun gizemli bir şekilde etrafta dolaştığı sessiz bir bellek sızıntısı?

Çoğu ikinciye cevap verebilir, ancak diyelim ki bu yazılım, muhtemelen günlerce saatlerce çalışacak şekilde tasarlandı ve bu eklenmiş Joe ve Jane’lerin her biri, gigabayt tarafından yazılımın bellek kullanımını artırıyor. Bu, kritik bir yazılım değil (çökmeler aslında kullanıcıları öldürmez), ancak performans açısından kritik bir yazılımdır.

Bu durumda, yaptığınız hatayı işaret ederek hata ayıklama sırasında hemen ortaya çıkan sert bir çarpışma, test prosedürünüzün radarı altında dahi uçabilecek sızdıran bir yazılım için tercih edilebilir.

Kapak tarafında, performansın amaç olmadığı, mümkün olan herhangi bir yöntemle çarpmamaları durumunda, kritik bir yazılımsa, sızıntı gerçekten tercih edilebilir.

Zayıf Referanslar

Zayıf referanslar olarak bilinen GC şemalarında bulunan bu fikirlerin bir melezi vardır. Zayıf referanslarla, bütün bu organizasyonlara, zayıf referans Joe’ya sahip olabiliriz, ancak güçlü referans (Joe’nun sahibi / yaşam çizgisi) ortadan kalktığında çıkarılmasını engelleyemeyiz. Bununla birlikte, Joe'nun artık bu zayıf referanslar arasında ne zaman artık bulunmadığını saptayabilmenin avantajlarından yararlanıyoruz, bu da kolayca tekrarlanabilen bir hata türünü almamızı sağlıyor.

Ne yazık ki, zayıf referanslar, neredeyse muhtemelen kullanılması gerektiği kadar kullanılmaz, bu nedenle çoğu zaman, bir kompleks C uygulamasından çok daha az cilalı olsalar bile, çoğu karmaşık GC uygulaması sızıntılara karşı duyarlı olabilir.

Her durumda, GC'nin hayatınızı kolaylaştırması veya zorlaştırması, yazılımınızın sızıntıları önlemesinin ne kadar önemli olduğuna ve bu tür karmaşık kaynak yönetimi ile ilgilenip ilgilenmediğine bağlıdır.

Benim durumumda, ben kaynaklar kullanıcıların gerçekten olabilir yukarıdaki çünkü böyle bir hatayı boşaltma isteği zaman o belleği serbest yayılma gigabayt megabayt yüzlerce ve yok bir performans açısından kritik alanda çalışmak az tercih yığılmasına. Çökmelerin tespit edilmesi ve çoğaltılması kolaydır, kullanıcının en sevdiği halde bile programcının en sevdiği türden bir hata haline gelir ve bu çökmelerin birçoğu kullanıcıya ulaşmadan önce aklı başında bir test prosedürü ortaya çıkar.

Neyse, bunlar GC ve manuel hafıza yönetimi arasındaki farklar. Anında sorunuzu yanıtlamak için, manuel bellek yönetiminin zor olduğunu söyleyebilirim, ancak sızıntılarla ilgisi çok az ve kaynak yönetimi önemsiz olduğunda hem GC hem de manuel bellek yönetimi biçimleri hala çok zor . GC, tartışmasız programın gayet iyi çalıştığı, ancak gittikçe daha fazla kaynak tükettiği durumlarda daha zor bir davranış sergiliyor. Manuel form daha az zor, ancak yukarıda gösterilen hatalar nedeniyle çok fazla zaman harcayacak ve çökecek.


-1

Bellek ile uğraşırken C ++ programcılarının karşılaştığı sorunların bir listesi:

  1. Kapsamlama sorunu yığın tahsisli bellekte ortaya çıkar: kullanım ömrü, tahsis edilen fonksiyonun dışına taşmaz. Bu problemin üç ana çözümü vardır: yığın hafızası ve tahsis noktasını çağrı yığınında yukarı doğru hareket ettirmek veya iç nesnelerden tahsis etmek .
  2. Büyüklük problemi , ayrılan ve iç nesneden ve kısmen de olsa ayrılmış bellekten tahsis edilen yığındadır : Bellek bloğunun büyüklüğü çalışma zamanında değişemez. Çözümler yığın bellek dizileri, işaretçiler ve kütüphaneler ve kaplar.
  3. Tanımlama sorununun sırası, iç nesnelerden ayrılırken: programın içindeki sınıfların doğru sırada olması gerekir. Çözümler bir ağacı bağımlılıklarla sınırlandırıyor ve sınıfları yeniden sıralıyor, ileriye dönük beyanları ve işaretçileri ve yığın hafızasını kullanmıyor ve ileriye dönük beyanları kullanıyor.
  4. İç-Dış problem nesneye ayrılmış bellekte. Nesnelerin içindeki hafıza erişimi iki bölüme ayrılır, bir miktar hafıza bir nesnenin içindedir ve diğer hafıza bunun dışındadır ve programcıların bu karara göre kompozisyon veya referansları kullanmayı doğru seçmeleri gerekir. Çözümler doğru kararı veriyor, ya da işaretçiler ve yığın hafızası.
  5. Özyinelemeli nesneler sorunu nesneye ayrılmış bellekte. Aynı nesne kendi içine yerleştirilirse nesnelerin boyutu sonsuz olur ve çözümler referanslar, yığın belleği ve işaretçilerdir.
  6. Mülkiyet izleme sorunu yığın ayrılmış bellekte, yer ayırmış belleğin adresini içeren işaretçinin, ayırma noktasından bırakma noktasına geçirilmesi gerekir. Çözümler yığın ayrılmış bellek, nesne tahsisli bellek, auto_ptr, shared_ptr, unique_ptr, stdlib kapsayıcılarıdır.
  7. Mülkiyet çoğaltma sorunu yığın ayrılmış bellekte: tahsisat yalnızca bir kez yapılabilir. Çözümler yığın ayrılmış bellek, nesne ayrılmış bellek, auto_ptr, shared_ptr, unique_ptr, stdlib kapsayıcılarıdır.
  8. Boş işaretçi problemi ayrılmış bellektedir: işaretçilerin çoğu çalışma zamanında çökmesine neden olacak şekilde NULL olmasına izin verilir. Çözümler yığın bellek, nesneye ayrılmış bellek ve yığın alanlarının ve referansların dikkatli analizidir.
  9. Bellek sızıntısı sorunu yığın tahsis edilmiş bellekte: Tahsis edilen her bellek bloğu için silme çağrısı. Çözümler valgrind gibi araçlardır.
  10. Yığın taşması sorunu , yığın belleği kullanan özyinelemeli işlev çağrıları içindir. Normalde yığın boyutu, özyinelemeli algoritmalar hariç, derleme zamanında tamamen belirlenir. İşletim sisteminin yığın boyutunu yanlış tanımlamak da bu soruna neden olur çünkü gerekli yığın alanı boyutunu ölçmenin bir yolu yoktur.

Gördüğünüz gibi, yığın belleği birçok mevcut sorunu çözüyor, ancak ek bir karmaşıklığa neden oluyor. GC, bu karmaşıklığın bir kısmını işlemek için tasarlanmıştır. (Bazı problem isimleri bu problemler için doğru isimler değilse üzgünüm - bazen doğru ismi bulmak zor olabilir)


1
-1: Sorunun cevabı değil.
Sjoerd
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.