Bir işaretçiyi sildikten sonra NULL yapmak iyi bir uygulama mıdır?


154

Akıllı işaretçiler kullanın diyerek başlayacağım ve bunun için asla endişelenmenize gerek kalmayacak.

Aşağıdaki kodla ilgili sorunlar nelerdir?

Foo * p = new Foo;
// (use p)
delete p;
p = NULL;

Bu, bir cevap ve başka bir soruya yapılan yorumlarla ateşlendi. İtibaren bir açıklama Neil Butterworth birkaç upvotes oluşturulan:

Silme işleminden sonra işaretçilerin NULL olarak ayarlanması, C ++ 'da evrensel bir iyi uygulama değildir. Yapmanın iyi olduğu ve anlamsız olduğu ve hataları gizleyebildiği zamanlar vardır.

Yardımcı olamayacağı birçok durum var. Ama deneyimlerime göre, acıtamaz. Biri beni aydınlatsın.


6
@Andre: Teknik olarak tanımsız. Olası olan şey, eskisi gibi aynı belleğe erişmenizdir, ancak şimdi başka bir şey tarafından kullanılabilir. Belleği iki kez silerseniz, program yürütmenizi bulması zor bir şekilde bozma olasılığı yüksektir. deleteBoş gösterici için güvenlidir , bu da bir işaretçiyi sıfırlamanın iyi olmasının bir nedenidir.
David Thornley

5
@ André Pena, tanımsız. Çoğu zaman tekrarlanamaz. Hata ayıklama sırasında hatayı daha görünür hale getirmek ve belki daha tekrarlanabilir hale getirmek için işaretçiyi NULL olarak ayarlarsınız.
Mark Ransom

3
@ André: Kimse bilmiyor. Tanımsız Davranış. Erişim ihlali ile çökebilir veya uygulamanın geri kalanı tarafından kullanılan belleğin üzerine yazabilir. Dil standardı ne olacağına dair hiçbir garanti vermez ve bu nedenle uygulamanıza bir kez gerçekleştiğinde güvenemezsiniz. Bu olabilir nükleer füze ateşledi veya sabitdisk biçimlendirmiş. uygulamanızın belleğini bozabilir veya iblislerin burnunuzdan uçmasına neden olabilir. Bütün bahisler kapalı.
jalf

16
Uçan iblisler bir özelliktir, böcek değil.
jball

3
Bu soru tekrar değil çünkü diğer soru C ile ilgili ve bu da C ++ ile ilgili. Cevapların çoğu, C ++ 'da bulunmayan akıllı işaretçiler gibi şeylere dayanıyor.
Adrian McCarthy

Yanıtlar:


91

Bir göstericinin 0 olarak ayarlanması (standart C ++ 'da "null", C'den NULL tanımlaması biraz farklıdır) çift silme işlemlerinde çökmeleri önler.

Aşağıdakileri göz önünde bulundur:

Foo* foo = 0; // Sets the pointer to 0 (C++ NULL)
delete foo; // Won't do anything

Buna karşılık:

Foo* foo = new Foo();
delete foo; // Deletes the object
delete foo; // Undefined behavior 

Başka bir deyişle, silinen işaretçileri 0 olarak ayarlamazsanız, çift silme işlemi yaparsanız başınız derde girer. Silme işleminden sonra işaretçilerin 0 olarak ayarlanmasına karşı bir argüman, bunu yapmanın çift silme hatalarını maskelemesi ve onları işlenmeden bırakmasıdır.

Açıkçası çift silme hatasına sahip olmamak en iyisidir, ancak sahiplik anlamlarına ve nesne yaşam döngülerine bağlı olarak, bunu pratikte başarmak zor olabilir. UB'ye göre maskeli çift silme hatasını tercih ederim.

Son olarak, nesne tahsisini yönetmeyle ilgili bir not , ihtiyaçlarınıza bağlı olarak std::unique_ptrkatı / tekil sahiplik, std::shared_ptrpaylaşılan sahiplik veya başka bir akıllı işaretçi uygulamasına göz atmanızı öneririm .


13
Uygulamanız her zaman çift silme işleminde kilitlenmez. İki silme arasında ne olduğuna bağlı olarak her şey olabilir. Büyük olasılıkla, yığınınızı bozarsınız ve bir noktada tamamen ilgisiz bir kod parçasında çökersiniz. Segfault genellikle hatayı sessizce görmezden gelmekten daha iyi olsa da, bu durumda segfault garanti edilmez ve şüpheli bir faydası vardır.
Adam Rosenfield

29
Buradaki sorun, iki kez silme işlemine sahip olmanızdır. İşaretçiyi NULL yapmak, onu düzeltmediği veya daha güvenli hale getirmediği gerçeğini gizler. Bir yıl sonra geri gelen ve foo'nun silindiğini gören bir mainainer hayal edin. Artık işaretçiyi yeniden kullanabileceğine inanıyor, ne yazık ki ikinci silme işlemini kaçırabilir (aynı işlevde olmayabilir) ve şimdi işaretçinin yeniden kullanımı artık ikinci silme ile çöpe atılıyor. İkinci silme işleminden sonra herhangi bir erişim artık büyük bir sorundur.
Martin York

11
İşaretçinin ayarının NULLçift ​​silme hatasını maskeleyebileceği doğrudur . (Bazıları bu maskenin aslında bir çözüm olduğunu düşünebilir - bu, ancak sorunun kökenine inmediği için çok iyi bir maske değil.) Ama onu NULL maskeler olarak ayarlamamak uzak (UZAK!) Daha fazla verilere silindikten sonra erişmede yaygın sorunlar.
Adrian McCarthy

AFAIK, std :: auto_ptr yaklaşan c ++ standardında kullanımdan kaldırıldı
rafak

Kullanımdan kaldırıldı demezdim, bu fikir sanki gitmiş gibi geliyor. Aksine, 's varlık ile değiştirilir unique_ptryapar, hangi auto_ptrhareket semantik ile yapmaya çalıştı.
GManNickG

57

İşaretçileri sildikten sonra NULL olarak ayarlamak kesinlikle zarar veremez, ancak genellikle daha temel bir soruna göre biraz yara bandıdır: Neden ilk etapta bir işaretçi kullanıyorsunuz? İki tipik neden görebiliyorum:

  • Sadece yığın üzerinde bir şey tahsis edilmesini istediniz. Bu durumda, onu bir RAII nesnesine sarmak çok daha güvenli ve daha temiz olurdu. Artık nesneye ihtiyacınız kalmadığında RAII nesnesinin kapsamını sonlandırın. İşte böyle std::vectorçalışır ve yanlışlıkla işaretçileri etrafındaki boşaltılan belleğe bırakma sorununu çözer. Hiçbir işaret yok.
  • Ya da belki bazı karmaşık ortak sahiplik semantiği istiyordunuz. Geri dönen işaretçi , çağrılanla newaynı olmayabilir delete. Bu arada nesneyi aynı anda birden fazla nesne kullanmış olabilir. Bu durumda, paylaşılan bir işaretçi veya benzeri bir şey tercih edilebilirdi.

Genel kuralım, kullanıcı kodunda işaretçiler bırakırsanız, Yanlış Yapıyorsunuzdur. İşaretçi ilk etapta çöpü gösterecek şekilde orada olmamalıdır. Neden geçerliliğini sağlamak için sorumluluk alan bir nesne yok? İşaret edilen nesne bittiğinde kapsamı neden bitmiyor?


15
Yani ilk etapta ham bir gösterici olmaması gerektiğini ve söz konusu işaretçiyi içeren herhangi bir şeyin "iyi uygulama" terimiyle kutsanmaması gerektiğini mi savunuyorsunuz? Yeterince adil.
Mark Ransom

7
Eh, aşağı yukarı. Ham bir işaretçi içeren hiçbir şeyin iyi bir uygulama olarak adlandırılamayacağını söylemem . Sadece kuraldan ziyade istisna olduğu. Genellikle, işaretçinin varlığı, daha derin bir düzeyde bir şeylerin ters gittiğinin bir göstergesidir.
jalf

3
ancak acil soruyu yanıtlamak için hayır, işaretçileri null olarak ayarlamanın nasıl hatalara neden olabileceğini anlamıyorum .
jalf

7
Katılmıyorum - bir işaretçinin kullanımının iyi olduğu durumlar vardır. Örneğin, yığında 2 değişken var ve bunlardan birini seçmek istiyorsunuz. Veya isteğe bağlı bir değişkeni bir işleve geçirmek istiyorsunuz. Diyorum ki, asla ham işaretçi ile birlikte kullanmamalısınız new.
rlbond

4
bir işaretçi kapsam dışına çıktığında, herhangi bir şeyin veya herhangi birinin bununla nasıl başa çıkması gerektiğini anlamıyorum.
jalf

43

Daha da iyi bir en iyi uygulamam var: Mümkünse değişkenin kapsamını sonlandırın!

{
    Foo* pFoo = new Foo;
    // use pFoo
    delete pFoo;
}

17
Evet, RAII senin arkadaşın. Bir sınıfa sarın ve daha da basit hale gelir. Ya da STL'yi kullanarak hafızayı kendi başınıza işlemeyin!
Brian

24
Evet, gerçekten, bu en iyi seçenek. Yine de soruya cevap vermiyor.
Mark Ransom

3
Bu sadece işlev kapsamları dönemini kullanmanın bir yan ürünü gibi görünüyor ve sorunu gerçekten ele almıyor. İşaretçiler kullandığınızda, genellikle bunların kopyalarını birkaç katman derinliğine aktarırsınız ve ardından, sorunu çözmek için yönteminiz gerçekten anlamsızdır. İyi tasarımın hataları izole etmenize yardımcı olacağına katılıyorum, ancak yönteminizin bu amaca yönelik birincil araç olduğunu düşünmüyorum.
San Jacinto

2
Bir düşünün, eğer bunu yapabilirseniz, neden yığını unutup tüm hafızanızı yığından çekmeyesiniz?
San Jacinto

4
Örneğim kasıtlı olarak minimaldir. Örneğin, yeni yerine, belki nesne bir fabrika tarafından yaratılmıştır, bu durumda yığına giremez. Ya da belki kapsamın başında yaratılmamış, ancak bir yapıda yer almaktadır. Gösterdiğim şey, bu yaklaşımın derleme zamanında göstericinin herhangi bir kötüye kullanımını bulacağı , oysa NULLing'in çalışma zamanında herhangi bir kötüye kullanım bulacağıdır .
Don Neufeld

32

Gösterdiği nesneyi / nesneleri sildikten sonra her zaman bir işaretçiyi NULL(şimdi nullptr) olarak ayarlıyorum .

  1. Serbest bırakılan belleğe yönelik birçok referansı yakalamaya yardımcı olabilir (platform arızalarınızın boş göstericinin derefinde olduğunu varsayarak).

  2. Örneğin, ortalıkta bulunan işaretçinin kopyaları varsa, boş belleğe yapılan tüm referansları yakalayamaz. Ama bazıları hiç yoktan iyidir.

  3. Çift silmeyi maskeleyecek, ancak bunların zaten boşaltılan belleğe erişimden çok daha az yaygın olduğunu görüyorum.

  4. Çoğu durumda, derleyici onu optimize eder. Yani gereksiz olduğu argümanı beni ikna etmiyor.

  5. Halihazırda RAII kullanıyorsanız, deletekodunuzda başlamak için çok fazla s yoktur, bu nedenle fazladan atamanın dağınıklığa neden olduğu argümanı beni ikna etmez.

  6. Hata ayıklama sırasında eski bir işaretçi yerine boş değeri görmek genellikle uygundur.

  7. Bu hala sizi rahatsız ediyorsa, bunun yerine akıllı bir işaretçi veya referans kullanın.

Ayrıca, kaynak serbest olduğunda (tipik olarak yalnızca kaynağı kapsüllemek için yazılmış bir RAII sarmalayıcısının yok edicisinde) diğer kaynak tanıtıcı türlerini kaynaksız değere ayarlıyorum.

Büyük (9 milyon ifade) bir ticari ürün üzerinde çalıştım (esas olarak C'de). Bir noktada, bellek serbest kaldığında işaretçiyi boşa çıkarmak için makro büyüsünü kullandık. Bu, derhal düzeltilen birçok gizlenen hatayı ortaya çıkardı. Hatırlayabildiğim kadarıyla hiçbir zaman çifte özgür bir hatamız olmadı.

Güncelleme: Microsoft, bunun güvenlik için iyi bir uygulama olduğuna inanır ve uygulamayı SDL politikalarında tavsiye eder. Görünüşe göre MSVC ++ 11 , / SDL seçeneğiyle derlerseniz, silinen işaretçiyi otomatik olarak (birçok durumda) durduracaktır .


12

İlk olarak, bu ve yakından ilgili konularda birçok mevcut soru var, örneğin Neden işaretçiyi NULL olarak ayarlamıyor? .

Kodunuzda, ne olduğu sorunu (p kullanın). Örneğin, bir yerde böyle bir kodunuz varsa:

Foo * p2 = p;

sonra p2'yi NULL'a ayarlamak çok az şey başarır, çünkü hala endişelenecek p2 işaretçisine sahip olursunuz.

Bu, bir göstericinin NULL olarak ayarlanmasının her zaman anlamsız olduğu anlamına gelmez. Örneğin, p, ömrü p içeren sınıfla tam olarak aynı olmayan bir kaynağa işaret eden bir üye değişkendi ise, p'yi NULL olarak ayarlamak, kaynağın varlığını veya yokluğunu belirtmek için yararlı bir yol olabilir.


1
Yardımcı olamayacağı zamanlar olduğunu kabul ediyorum, ancak aktif olarak zararlı olabileceğini ima ediyor gibiydin. Niyetin bu muydu yoksa yanlış mı okudum?
Mark Ransom

1
İşaretçinin bir kopyasının olup olmadığı, işaretçi değişkeninin NULL olarak ayarlanıp ayarlanmayacağı sorusuyla ilgisizdir. NULL olarak ayarlamak, aynı gerekçeyle, akşam yemeğini bitirdikten sonra bulaşıkları temizlemek iyi bir uygulamadır - bir kodun sahip olabileceği tüm hatalara karşı bir koruma olmasa da, iyi kod sağlığını teşvik eder.
Franci Penov

2
@Franci Pek çok insan sana karşı çıkıyor. Orijinali sildikten sonra kopyayı kullanmaya çalışırsanız bir kopyanın olup olmadığı kesinlikle önemlidir.

3
Franci, bir fark var. Bulaşıkları temizliyorsun çünkü tekrar kullanıyorsun. İşaretçiyi sildikten sonra ihtiyacınız yoktur. Yapacağın son şey bu olmalı. Daha iyi uygulama, durumdan tamamen kaçınmaktır.
GManNickG

1
Bir değişkeni yeniden kullanabilirsiniz, ancak bu artık bir savunma programlama durumu değildir; elinizdeki sorunun çözümünü bu şekilde tasarladınız. OP, bu savunma tarzının bizim için çaba göstermemiz gereken bir şey olup olmadığını tartışıyor, hiçbir zaman bir göstericiyi boşa ayarlayamayacağız. Ve ideal olarak, sorunuza evet! İşaretçileri sildikten sonra kullanmayın!
GManNickG

7

Daha sonra kod varsa delete, Evet İşaretçi bir yapıcıda veya yöntem ya da işlevin sonunda silindiğinde No.

Bu benzetmenin amacı, programcıya çalışma sırasında nesnenin zaten silinmiş olduğunu hatırlatmaktır.

Daha da iyi bir uygulama, hedef nesnelerini otomatik olarak silen Akıllı İşaretçileri (paylaşılan veya kapsamlı) kullanmaktır.


Herkes (orijinal soru soran dahil) akıllı işaretçilerin gidilecek yol olduğunu kabul eder. Kod gelişir. Silme işleminden sonra ilk kez düzelttiğinizde daha fazla kod olmayabilir, ancak bu zamanla değişebilir. Görevi vermek, bu olduğunda yardımcı olur (ve bu arada neredeyse hiçbir maliyeti yoktur).
Adrian McCarthy

3

Başkalarının dediği gibi, delete ptr; ptr = 0;iblislerin burnunuzdan uçmasına neden olmayacak. Ancak, ptrbir çeşit bayrak olarak kullanılmasını teşvik eder . Kod dağınık hale gelir deleteve işaretçiyi olarak ayarlar NULL. Bir sonraki adım, if (arg == NULL) return;bir NULLişaretçinin yanlışlıkla kullanılmasını önlemek için kodunuzu dağıtmaktır . Sorun, kontroller NULLbir nesnenin veya programın durumunu kontrol etmenin birincil yolu haline geldiğinde ortaya çıkar .

Bir yerde işaretçi bayrak olarak kullanmakla ilgili bir kod kokusu olduğundan eminim ama bulamadım.


9
İşaretçiyi bayrak olarak kullanmanın yanlış bir tarafı yoktur. Bir işaretçi kullanıyorsanız ve NULLgeçerli bir değer değilse, muhtemelen bunun yerine bir referans kullanmalısınız.
Adrian McCarthy

2

Sorunuzu biraz değiştireceğim:

Başlatılmamış bir işaretçi kullanır mısınız? Bilirsiniz, NULL olarak ayarlamadığınız veya işaret ettiği belleği tahsis etmediniz mi?

İşaretçinin NULL olarak ayarlanmasının atlanabileceği iki senaryo vardır:

  • işaretçi değişkeni anında kapsam dışına çıkar
  • işaretçinin anlamını aşırı yüklediniz ve değerini yalnızca bir bellek işaretçisi olarak değil, aynı zamanda bir anahtar veya ham değer olarak da kullanıyorsunuz. ancak bu yaklaşım başka sorunlardan muzdariptir.

Bu arada, işaretçiyi NULL olarak ayarlamanın hataları gizleyebileceğini savunmak, bana bir hatayı düzeltmemeniz gerektiğini savunmak gibi geliyor çünkü düzeltme başka bir hatayı gizleyebilir. İşaretçinin NULL olarak ayarlanmaması durumunda gösterilebilecek tek hata, işaretçiyi kullanmaya çalışanlar olacaktır. Ama onu NULL olarak ayarlamak, eğer onu serbest hafıza ile kullanırsanız göstereceğiniz hatanın tam olarak aynı olmasına neden olur, değil mi?


(A) "bir hatayı düzeltmemeniz gerektiğini savunuyor gibi geliyor" İşaretçiyi NULL olarak ayarlamamak bir hata değildir. (B) "Ancak NULL olarak ayarlamak aslında tam olarak aynı hataya neden olur" Hayır. NULL olarak ayarlamak çift ​​silmeyi gizler . (C) Özet: NULL olarak ayarlamak, çift silmeyi gizler, ancak eski referansları ortaya çıkarır. NULL olarak ayarlanmaması eski referansları gizleyebilir, ancak çift silmeleri ortaya çıkarır. Her iki taraf da asıl sorunun eski referansları ve çift silmeleri düzeltmek olduğu konusunda hemfikir.
Mooing Duck

2

Sildikten sonra işaretçiyi NULL olarak ayarlamanıza veya ayarlamamanıza neden olacak başka bir kısıtlamanız yoksa (böyle bir kısıtlamadan Neil Butterworth tarafından bahsedilmiştir ), kişisel tercihim onu ​​öyle bırakmaktır.

Benim için soru "bu iyi bir fikir mi?" Değil ancak "Bunu yaparak hangi davranışı engellerim veya başarılı olmasına izin veririm?" Örneğin, bu, diğer kodun işaretçinin artık kullanılamadığını görmesine izin veriyorsa, neden diğer kodlar serbest bırakıldıktan sonra serbest bırakılmış işaretçilerine bakmaya çalışıyor? Genellikle bu bir hatadır.

Ayrıca gereğinden fazla iş yapar ve ölüm sonrası hata ayıklamayı engeller. İhtiyaç duymadığınız zaman belleğe ne kadar az dokunursanız, bir şeyin neden çöktüğünü anlamak o kadar kolay olur. Çoğu zaman, belleğin, söz konusu hatayı teşhis etmek ve düzeltmek için belirli bir hatanın meydana geldiği zamana benzer bir durumda olduğu gerçeğine güvendim.


2

Silme işleminden sonra açıkça boş bırakılması, okuyucuya işaretçinin kavramsal olarak isteğe bağlı bir şeyi temsil ettiğini kuvvetle önerir . Bunun yapıldığını görürsem, işaretçinin kaynağın her yerinde ilk önce NULL'a karşı test edilmesi gerektiğinden endişelenmeye başlardım.

Aslında kastettiğin buysa, bunu kaynakta boost :: Optional gibi bir şey kullanarak açıkça belirtmek daha iyidir

optional<Foo*> p (new Foo);
// (use p.get(), but must test p for truth first!...)
delete p.get();
p = optional<Foo*>();

Ancak insanların işaretçinin "kötüye gittiğini" bilmesini gerçekten istiyorsanız, yapılacak en iyi şeyin kapsam dışına çıkmak olduğunu söyleyenlerle% 100 anlaşma sağlayacağım. Ardından, çalışma zamanında kötü başvurular olasılığını önlemek için derleyiciyi kullanıyorsunuz.

Tüm C ++ banyo suyundaki bebek bu, onu atmamalı. :)


2

Uygun hata kontrolü ile iyi yapılandırılmış programda, hiçbir neden yoktur değil boş atamak için. 0bu bağlamda evrensel olarak kabul edilen geçersiz bir değer olarak tek başına duruyor. Zor başarısız olur ve yakında başarısız olur.

Görevlendirilmesi karşı argümanlar çoğu 0bunun düşündürmektedir olabilir Hata veya komplike kontrol akışını gizler. Temelde, bu bir yukarı akış hatası (sizin hatanız değil (kötü kelime için özür dilerim) veya programcı adına başka bir hata - belki de program akışının çok karmaşık hale geldiğinin bir göstergesi.

Programcı, özel bir değer olarak boş olabilecek bir göstericinin kullanımını tanıtmak ve bunun etrafına gerekli tüm kaçışları yazmak istiyorsa, bu kasıtlı olarak ortaya koydukları bir komplikasyondur. Karantina ne kadar iyi olursa kötüye kullanım vakalarını o kadar çabuk bulur ve diğer programlara o kadar az yayılabilir.

Bu durumlardan kaçınmak için iyi yapılandırılmış programlar C ++ özellikleri kullanılarak tasarlanabilir. Referansları kullanabilir veya sadece "boş veya geçersiz argümanların iletilmesi / kullanılması bir hatadır" diyebilirsiniz - akıllı işaretçiler gibi kapsayıcılar için de aynı şekilde geçerli bir yaklaşım. Tutarlı ve doğru davranışın artması, bu hataların uzaklaşmasını engeller.

Oradan, boş göstericinin var olabileceği (veya izin verildiği) çok sınırlı bir kapsam ve bağlama sahip olursunuz.

Aynısı olmayan işaretçiler için de geçerli olabilir const. Bir işaretçinin değerini takip etmek önemsizdir çünkü kapsamı çok küçüktür ve yanlış kullanım kontrol edilir ve iyi tanımlanır. Araç setiniz ve mühendisleriniz hızlı bir okumayı takiben programı takip edemiyorsa veya uygun olmayan hata kontrolü veya tutarsız / esnek program akışı varsa, daha büyük başka sorunlarınız var demektir.

Son olarak, derleyiciniz ve ortamınız, hatalar (karalama) eklemek, serbest belleğe erişimi tespit etmek ve diğer ilgili UB'leri yakalamak istediğiniz zamanlar için muhtemelen bazı korumalara sahiptir. Ayrıca, genellikle mevcut programları etkilemeden programlarınıza benzer tanılamalar ekleyebilirsiniz.


1

Sorunuza zaten koyduğunuz şeyi genişletmeme izin verin.

İşte sorunuza madde işareti biçiminde koyduğunuz şey:


Silme işleminden sonra işaretçilerin NULL olarak ayarlanması, C ++ 'da evrensel bir iyi uygulama değildir. Şu zamanlar vardır:

  • yapmak iyi bir şey
  • ve anlamsız olduğu ve hataları gizleyebildiği zamanlar.

Ancak bunun kötü olduğu zamanlar yoktur ! Sen olacak değil açıkça battal tarafından fazla hata tanıtmak, sen olmayacak sızıntı bellek, sen olmayacak tanımsız davranış nedeni gerçekleşmesi.

Yani, şüpheniz varsa, onu boş bırakın.

Bunu söyledikten sonra, açıkça bir işaretçi boş bırakmanız gerektiğini düşünüyorsanız, bu bana bir yöntemi yeterince bölmemişsiniz gibi geliyor ve yöntemi ayırmak için "Çıkartma yöntemi" adlı yeniden düzenleme yaklaşımına bakmalısınız. ayrı parçalar.


"Bunun kötü olduğu zamanlar yoktur" diye katılmıyorum. Bu deyimin getirdiği kesicinin miktarını bir düşünün. Bir şeyi silen her birime dahil edilen bir başlığınız var ve tüm bu silme konumları biraz daha kolay hale geliyor .
GManNickG

Kötü olduğu zamanlar vardır. Birisi, silinmesi gerektiği halde, silinen-şimdi-boş-işaretçinizi kaldırmaya çalışırsa, muhtemelen çökmez ve bu hata 'gizlidir'. İçinde hala rasgele bir değere sahip olan silinmiş işaretçinize başvurmazlarsa, muhtemelen fark edeceksiniz ve hatanın görülmesi daha kolay olacaktır.
Carson Myers

@Carson: Benim deneyimim tam tersi: Bir nullptr'nin referansının kaldırılması neredeyse tüm yollarla Uygulamayı çöker ve bir hata ayıklayıcı tarafından yakalanabilir. Sarkan bir işaretçinin referansının kaldırılması genellikle hemen bir sorun oluşturmaz, ancak genellikle yanlış sonuçlara veya diğer hatalara yol açar.
MikeMB

@MikeMB Tamamen katılıyorum, bununla ilgili görüşlerim geçmişte büyük ölçüde değişti ~ 6.5 yıl
Carson Myers

Programcı olma açısından, 6-7 yıl önce hepimiz başka biriydik :) Bugün bir C / C ++ sorusunu yanıtlamaya cesaret edemeyeceğime bile emin değilim :)
Lasse V.Karlsen

1

Evet.

Yapabileceği tek "zarar", programınıza verimsizlik (gereksiz bir depolama işlemi) sokmaktır - ancak bu ek yük, çoğu durumda bellek bloğunu ayırma ve serbest bırakma maliyetiyle ilişkili olarak önemsiz olacaktır.

Bunu yapmazsanız, bir gün bazı kötü işaretçi derefernce hatalarınız olacak .

Her zaman silmek için bir makro kullanırım:

#define SAFEDELETE(ptr) { delete(ptr); ptr = NULL; }

(ve bir dizi için benzer, free (), tutamaçları serbest bırakarak)

Ayrıca, çağıran kodun işaretçisini çağıran kodun işaretçisini NULL'a zorlayacak şekilde, çağıran kodun işaretçisine referans alan "kendi kendini silme" yöntemleri de yazabilirsiniz. Örneğin, birçok nesneden oluşan bir alt ağacı silmek için:

static void TreeItem::DeleteSubtree(TreeItem *&rootObject)
{
    if (rootObject == NULL)
        return;

    rootObject->UnlinkFromParent();

    for (int i = 0; i < numChildren)
       DeleteSubtree(rootObject->child[i]);

    delete rootObject;
    rootObject = NULL;
}

Düzenle

Evet, bu teknikler makro kullanımıyla ilgili bazı kuralları ihlal ediyor (ve evet, bugünlerde muhtemelen aynı sonucu şablonlarla elde edebilirsiniz) - ancak yıllar boyunca hiç ölü belleğe erişmedim - en kötü ve en zor olanlardan biri ve karşılaşabileceğiniz sorunları ayıklamak en çok zaman alır. Yıllar boyunca pratikte, onları tanıttığım her takımdaki bütün bir hata sınıfını etkili bir şekilde ortadan kaldırdılar.

Yukarıdakileri uygulayabileceğiniz birçok yol vardır - sadece, arayanın işaretçisini BOŞLUK olmayan belleği serbest bırakmaları için bir yol sağlamak yerine, insanları bir nesneyi sildiklerinde bir işaretçiyi NULL yapmaya zorlama fikrini göstermeye çalışıyorum. .

Tabii ki, yukarıdaki örnek bir otomatik işaretleyiciye doğru bir adımdır. Bunu önermedim çünkü OP özellikle bir otomatik işaretçi kullanmama durumunu soruyordu.


2
Makrolar, özellikle normal işlevler gibi göründüklerinde kötü bir fikirdir. Bunu yapmak istiyorsanız şablonlu bir işlev kullanın.

2
Vay canına ... İşaretçiyi anObject->Delete(anObject)geçersiz kılan hiçbir şey görmedim anObject. Bu sadece korkutucu. En TreeItem::Delete(anObject)azından yapmaya zorlanmanız için bunun için statik bir yöntem oluşturmalısınız .
D.Shawley

Maalesef, büyük harfli "bu bir makrodur" biçimini kullanmak yerine bir işlev olarak yazdım. Düzeltildi. Kendimi daha iyi anlatmak için bir de yorum ekledim.
Jason Williams

Ve haklısın, benim çabucak bozulan örneğim saçmalıktı! Düzeltildi :-). Bu fikri açıklamak için sadece hızlı bir örnek düşünmeye çalışıyordum: bir işaretçiyi silen herhangi bir kod, başka biri (arayan) bu işaretçiye sahip olsa bile işaretçinin NULL olarak ayarlanmasını sağlamalıdır. Öyleyse her zaman işaretçiye bir başvuru iletin, böylece silme noktasında NULL'a zorlanabilir.
Jason Williams

1

"Yapmanın iyi olduğu zamanlar ve anlamsız olduğu ve hataları gizleyebileceği zamanlar vardır"

İki problem görebiliyorum: Bu basit kod:

delete myObj;
myobj = 0

çok iş parçacıklı ortamda bir for-liner haline gelir:

lock(myObjMutex); 
delete myObj;
myobj = 0
unlock(myObjMutex);

Don Neufeld'in "en iyi uygulaması" her zaman geçerli değildir. Örneğin, bir otomotiv projesinde, yıkıcılarda bile işaretçileri 0'a ayarlamak zorunda kaldık. Güvenlik açısından kritik yazılımlarda bu tür kuralların nadir olmadığını hayal edebiliyorum. Koddaki her işaretçi kullanımı için ekibi / kod denetleyiciyi bu işaretçiyi boş bırakan bir satırın gereksiz olduğuna ikna etmeye çalışmaktan daha kolaydır (ve akıllıca).

Başka bir tehlike de, istisnalarda bu tekniğe güvenmektir - kod kullanma:

try{  
   delete myObj; //exception in destructor
   myObj=0
}
catch
{
   //myObj=0; <- possibly resource-leak
}

if (myObj)
  // use myObj <--undefined behaviour

Böyle bir kodda ya kaynak sızıntısı üretirsiniz ve sorunu ertelersiniz ya da süreç çöker.

Yani, kafamdan kendiliğinden geçen bu iki problem (Herb Sutter kesinlikle daha fazlasını anlatırdı) bana "Akıllı işaretçilerden nasıl kaçınılır ve işi normal işaretçilerle nasıl güvenli bir şekilde yapılır?" Türündeki tüm soruları eskimiş gibi yapar.


4-astarın 3-astardan ne kadar önemli ölçüde daha karmaşık olduğunu (yine de kilit korumaları kullanılmalı) ve yıkıcınız fırlatırsa zaten başınız belada demektir.
MikeMB

Bu cevabı ilk gördüğümde, neden bir imleci bir yıkıcıda sıfırlamak isteyeceğinizi anlamadım, ama şimdi anlıyorum - bu , işaretçiye sahip olan nesnenin silindikten sonra kullanıldığı durum içindir!
Mark Ransom


0

Eğer işaretçiyi tekrar kullanmadan önce yeniden tahsis edecekseniz (referansını kaldırmak, bir işleve geçirmek, vb.), İşaretçiyi NULL yapmak sadece ekstra bir işlemdir. Ancak, tekrar kullanılmadan önce yeniden tahsis edilip edilmeyeceğinden emin değilseniz, NULL olarak ayarlamak iyi bir fikirdir.

Birçoğunun dediği gibi, sadece akıllı işaretçiler kullanmak elbette çok daha kolay.

Düzenleme: Thomas Matthews'ın bu önceki cevapta söylediği gibi , eğer bir imleç bir yıkıcıda silinirse, nesne zaten yok edildiği için tekrar kullanılmayacağı için ona NULL atamaya gerek yoktur.


0

Tek bir işlevde (veya nesnede) yeniden kullanmanın meşru bir senaryosunun olduğu nadir durumlarda yararlı olduğunu sildikten sonra bir işaretçiyi NULL olarak ayarlamayı hayal edebiliyorum . Aksi takdirde hiçbir anlam ifade etmez - bir göstericinin var olduğu sürece anlamlı bir şeye işaret etmesi gerekir - nokta.


0

Kod, uygulamanızın performans açısından en kritik bölümüne ait değilse, basit tutun ve bir shared_ptr kullanın:

shared_ptr<Foo> p(new Foo);
//No more need to call delete

Referans sayımı gerçekleştirir ve iş parçacığı açısından güvenlidir. Bunu tr1'de (std :: tr1 ad alanı, #include <bellek>) bulabilirsiniz veya derleyiciniz bunu sağlamazsa, yükseltmeden alın.

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.