Visual Studio silinmiş bir işaretçi ile ne yapar ve neden?


130

Okuduğum bir C ++ kitabı, deleteoperatör kullanılarak bir işaretçi silindiğinde işaret ettiği konumdaki belleğin "serbest bırakıldığını" ve üzerine yazılabileceğini belirtiyor. Ayrıca, işaretçinin yeniden atanana veya olarak ayarlanana kadar aynı konumu göstermeye devam edeceğini belirtir NULL.

Visual Studio 2012'de ise; durum böyle görünmüyor!

Misal:

#include <iostream>

using namespace std;

int main()
{
    int* ptr = new int;
    cout << "ptr = " << ptr << endl;
    delete ptr;
    cout << "ptr = " << ptr << endl;

    system("pause");

    return 0;
}

Bu programı derleyip çalıştırdığımda şu çıktıyı alıyorum:

ptr = 0050BC10
ptr = 00008123
Press any key to continue....

Silme çağrısı yapıldığında, işaretçinin işaret ettiği adres açıkça değişir!

Bu neden oluyor? Bunun özellikle Visual Studio ile bir ilgisi var mı?

Ve eğer silme, işaret ettiği adresi değiştirebiliyorsa, neden silme işlemi otomatik olarak işaretçiyi NULLrastgele bir adres yerine olarak ayarlamasın ?


4
İşaretçiyi silmek, NULL olarak ayarlanacağı anlamına gelmez, bununla ilgilenmeniz gerekir.
Matt

11
Bunu biliyorum, ancak özellikle okuduğum kitap, silinmeden önce işaret ettiği adresi yine de içereceğini söylüyor, ancak bu adresin içeriğinin üzerine yazılabilir.
tjwrona1992

6
@ tjwrona1992, evet, çünkü bu genellikle oluyor. Kitap, zor kuralı değil, en olası sonucu listeliyor.
SergeyA

5
@ tjwrona1992 Okuduğum bir C ++ kitabı - ve kitabın adı ...?
PaulMcKenzie

4
@ tjwrona1992: Şaşırtıcı olabilir, ancak tanımlanmamış bir davranış olan geçersiz işaretçi değerinin kullanımı, yalnızca referans alma değil. Değeri izin verilmeyen bir şekilde kullanıyor "Nereyi işaret ettiğinin kontrol edilmesi".
Ben Voigt

Yanıtlar:


175

Depolanan adresin ptrüzerine her zaman 00008123... ile yazıldığını fark ettim .

Bu tuhaf göründü, bu yüzden biraz araştırma yaptım ve "C ++ nesnelerini silerken otomatik işaretçi temizliği" ni tartışan bir bölüm içeren bu Microsoft blog gönderisini buldum .

... NULL için kontroller ortak bir kod yapısıdır, yani mevcut bir NULL kontrolünün bir temizleme değeri olarak NULL kullanılmasıyla birleştirilmesi, kök nedeni gerçekten ele alınması gereken gerçek bir bellek güvenliği sorununu tesadüfen gizleyebilir.

Bu nedenle, temizleme değeri olarak 0x8123'ü seçtik - işletim sistemi perspektifinden bu, sıfır adresle (NULL) aynı bellek sayfasındadır, ancak 0x8123'teki bir erişim ihlali, geliştiricinin daha ayrıntılı ilgiye ihtiyaç duyması nedeniyle daha iyi ön plana çıkacaktır. .

Yalnızca Visual Studio'nun işaretçi silindikten sonra ne yaptığını açıklamakla kalmaz, aynı zamanda neden onu NULLotomatik olarak ayarlamamayı seçtiklerini de yanıtlar !


Bu "özellik", "SDL kontrolleri" ayarının bir parçası olarak etkinleştirilir. Etkinleştirmek / devre dışı bırakmak için şuraya gidin: PROJECT -> Properties -> Configuration Properties -> C / C ++ -> General -> SDL checks

Bunu onaylamak için:

Bu ayarın değiştirilmesi ve aynı kodun yeniden çalıştırılması aşağıdaki çıktıyı üretir:

ptr = 007CBC10
ptr = 007CBC10

"özellik" tırnak içindedir, çünkü aynı konuma giden iki işaretçinizin olduğu bir durumda, silme işlevini çağırmak bunlardan yalnızca BİRİNİ sterilize eder. Diğeri geçersiz yere işaret edecek şekilde bırakılacak ...


GÜNCELLEME:

5 yıllık C ++ programlama deneyiminden sonra, tüm bu sorunun temelde tartışmalı bir konu olduğunun farkındayım. Bir C ++ programcısıysanız ve akıllı işaretçiler (bu sorunun tamamını aşan) kullanmak yerine hala ham işaretçileri kullanıyor newve deleteyönetiyorsanız, C programcısı olmak için kariyer yolunda bir değişiklik düşünmek isteyebilirsiniz. ;)


12
Bu güzel bir keşif. MS'nin böyle hata ayıklama davranışını daha iyi belgelemesini dilerdim. Örneğin, hangi derleyici sürümünün bunu uygulamaya başladığını ve hangi seçeneklerin davranışı etkinleştirdiğini / devre dışı bıraktığını bilmek güzel olurdu.
Michael Burr

5
"bir işletim sistemi perspektifinden bakıldığında, bu sıfır adresle aynı bellek sayfasındadır" - ha? X86'daki standart (büyük sayfaları göz ardı ederek) sayfa boyutu hem Windows hem de linux için hala 4kb değil mi? Raymond Chen'in blogundaki ilk 64 kb adres alanı hakkında bir şeyler hatırlamama rağmen, pratikte aynı sonucu alıyorum
Voo

12
@Voo windows, yakalama için ilk (ve son) 64kB değerinde RAM'i ölü alan olarak ayırır. 0x8123 oraya güzelce düşüyor
cırcır ucube

7
Aslında, kötü alışkanlıkları teşvik etmez ve işaretçiyi NULL olarak ayarlamayı atlamanıza izin vermez - 0x8123bunun yerine kullanmalarının nedeni budur 0. İşaretçi hala geçersiz olduğunu, ancak bunu (iyi) KQUEUE çalışırken bir duruma neden olur ve bu değildir (o yapmak değil, bir hata, çünkü aynı zamanda iyi) BOŞ kontrolleri geçmektedir. Kötü alışkanlıkların yeri neresi? Gerçekten hata ayıklamanıza yardımcı olacak bir şey.
Luaan

3
Her ikisini de (hepsini) ayarlayamaz, bu yüzden bu ikinci en iyi seçenektir. Beğenmediyseniz, SDL kontrollerini kapatın - özellikle başka birinin kodunda hata ayıklarken oldukça yararlı buluyorum.
Luaan

30

/sdlDerleme seçeneğinin yan etkilerini görüyorsunuz . VS2015 projeleri için varsayılan olarak açılır, / gs tarafından sağlananların ötesinde ek güvenlik kontrolleri sağlar. Değiştirmek için Proje> Özellikler> C / C ++> Genel> SDL kontrolleri ayarını kullanın.

MSDN makalesinden alıntı yapmak :

  • Sınırlı işaretçi temizliği gerçekleştirir. Başvurular içermeyen ifadelerde ve kullanıcı tanımlı yıkıcı içermeyen türlerde, işaretçi başvuruları, silme çağrısından sonra geçersiz bir adrese ayarlanır. Bu, eski işaretçi referanslarının yeniden kullanılmasını önlemeye yardımcı olur.

MSVC'yi kullandığınızda silinen işaretçileri NULL olarak ayarlamanın kötü bir uygulama olduğunu unutmayın. Hem Hata Ayıklama Yığından hem de bu / sdl seçeneğinden aldığınız yardımı yener, artık programınızda geçersiz ücretsiz / silme çağrılarını algılayamazsınız.


1
Onaylanmış. Bu özelliği devre dışı bıraktıktan sonra, işaretçi artık yeniden yönlendirilmez. Onu değiştiren gerçek ayarı sağladığınız için teşekkür ederiz!
tjwrona1992

Hans, aynı konuma işaret eden iki işaretçinin olduğu bir durumda silinmiş işaretçileri NULL olarak ayarlamak hala kötü bir uygulama olarak kabul ediliyor mu? Ne zaman deletebir, Visual Studio özgün konumuna ikinci işaretçi işaret bırakacaktır artık geçersiz olduğu.
tjwrona1992

1
İşaretçiyi NULL olarak ayarlayarak ne tür bir sihir olmasını beklediğiniz benim için oldukça belirsiz. Bu diğer işaretçi hiçbir şeyi çözmediği için değil, hatayı bulmak için yine de hata ayıklama ayırıcısına ihtiyacınız var.
Hans Passant

3
VS yok değil işaretçileri temizlemek. Onları yozlaştırır. Böylece, her halükarda kullandığınızda programınız çökecektir. Hata ayıklama ayırıcısı, yığın bellekle hemen hemen aynı şeyi yapar. NULL ile ilgili en büyük sorun, yeterince bozuk olmamasıdır. Aksi takdirde ortak bir strateji olan google "0xdeadbeef".
Hans Passant

1
İmleci NULL olarak ayarlamak, onu artık geçersiz olan önceki adresine işaret etmekten çok daha iyidir. Bir NULL işaretçisine yazmaya çalışmak hiçbir veriyi bozmaz ve muhtemelen programı çökertir. Bu noktada işaretçiyi yeniden kullanmaya çalışmak programı çökertmeyebilir, sadece çok öngörülemeyen sonuçlar üretebilir!
tjwrona1992

19

Ayrıca işaretçinin yeniden atanana veya NULL olarak ayarlanana kadar aynı konumu göstermeye devam edeceğini belirtir.

Bu kesinlikle yanıltıcı bilgidir.

Silme çağrısı yapıldığında, işaretçinin işaret ettiği adres açıkça değişir!

Bu neden oluyor? Bunun özellikle Visual Studio ile bir ilgisi var mı?

Bu açıkça dil şartnameleri dahilindedir. ptrçağrıldıktan sonra geçerli değildir delete. D ptrolduktan sonra kullanılması deletetanımsız davranışa neden olur. Yapma. Çalışma süresi ortamı, ptrçağrıdan sonra istediği her şeyi yapmakta özgürdür delete.

Ve eğer silme, işaret ettiği adresi değiştirebiliyorsa, neden silme işlemi otomatik olarak imleci rastgele bir adres yerine NULL olarak ayarlamasın ???

İşaretçinin değerinin herhangi bir eski değere değiştirilmesi dil spesifikasyonu dahilindedir. NULL olarak değiştirmeye gelince, bunun kötü olacağını söyleyebilirim. İşaretçinin değeri NULL olarak ayarlanmışsa, program daha mantıklı davranır. Ancak, bu sorunu gizleyecektir. Program farklı optimizasyon ayarlarıyla derlendiğinde veya farklı bir ortama taşındığında, sorun muhtemelen en uygun olmayan anda ortaya çıkacaktır.


1
OP'nin sorusuna cevap verdiğine inanmıyorum.
SergeyA

Düzenlemeden sonra bile katılmıyorum. NULL olarak ayarlamak sorunu gizlemeyecektir - aslında, o olmadan olduğundan daha fazla durumda EXPOSE olacaktır. Normal uygulamaların bunu yapmamasının bir nedeni vardır ve nedeni farklıdır.
SergeyA

4
@SergeyA, çoğu uygulama bunu verimlilik uğruna yapmıyor. Bununla birlikte, bir uygulama onu ayarlamaya karar verirse, onu NULL olmayan bir şeye ayarlamak daha iyidir. Sorunları, NULL olarak ayarlanmasından daha erken ortaya çıkaracaktır. NULL olarak ayarlanmıştır delete, işaretçide iki kez çağırmak soruna neden olmaz. Bu kesinlikle iyi değil.
R Sahu

Hayır, verimlilik değil - en azından asıl mesele bu değil.
SergeyA

7
@SergeyA Bir işaretçi NULL, sürecin adres alanının dışında olan ancak kesinlikle dışında olan bir değere ayarlanması , iki alternatiften daha fazla durum ortaya çıkaracaktır. Serbest bırakıldıktan sonra kullanılırsa sallanarak bırakmak mutlaka bir segfault'a neden olmaz; yeniden d NULLise bir segfault'a ayarlamak bir segfault'a neden olmaz delete.
Blacklight Shining

10
delete ptr;
cout << "ptr = " << ptr << endl;

Genelde , geçersiz işaretçilerin değerlerini okumak bile (yukarıda yaptığınız gibi, not: bu başvurudan farklıdır) geçersiz işaretçilerin değerlerini (örneğin, siz yaptığınızda işaretçi geçersiz hale gelir delete) uygulama tanımlı davranıştır. Bu, CWG # 1438'de tanıtıldı . Ayrıca buraya bakın .

Lütfen, geçersiz işaretçilerin değerlerinin okunmasından önce tanımsız davranış olduğunu, dolayısıyla yukarıda sahip olduğunuz şeyin tanımsız davranış olacağını, yani her şeyin olabileceği anlamına geldiğini unutmayın.


3
Ayrıca şu alıntı da geçerlidir [basic.stc.dynamic.deallocation]: "Standart kitaplıktaki bir serbest bırakma işlevine verilen bağımsız değişken, boş işaretçi değeri olmayan bir işaretçi ise, serbest bırakma işlevi, gösterici tarafından atıfta bulunulan depolamayı yeniden serbest bırakacak ve herhangi bir işaretçiyi geçersiz kılacaktır. serbest [conv.lval]bırakılmış depolamanın parçası "ve (bölüm 4.1) ' deki kural, herhangi bir geçersiz işaretçi değerinin okunmasını (lvalue-> rvalue dönüşümü) söyleyen uygulama tanımlı davranıştır.
Ben Voigt

UB bile, belirli bir satıcı tarafından, en azından o derleyici için güvenilir olacak şekilde belirli bir şekilde uygulanabilir. Microsoft, işaretçi temizleme özelliğini CWG # 1438'den önce uygulamaya karar vermiş olsaydı, bu, bu özelliği daha fazla veya daha az güvenilir hale getirmezdi ve özellikle, bu özellik açıldığında "her şeyin olabileceği" doğru değildir. , standardın ne söylediğine bakılmaksızın.
Kyle Strand

@KyleStrand: Temel olarak UB'nin tanımını verdim ( blog.regehr.org/archives/213 ).
giorgim

1
SO'daki C ++ topluluğunun çoğu için, "her şey olabilir" kelimesi tamamen fazla gerçek anlamda alınır . Bunun saçma olduğunu düşünüyorum . UB'nin tanımını anlıyorum, ancak aynı zamanda derleyicilerin gerçek insanlar tarafından uygulanan yazılım parçaları olduğunu da anlıyorum ve eğer bu kişiler derleyiciyi belirli bir şekilde davranacak şekilde uygularsa , standart ne derse desin derleyici böyle davranacaktır . .
Kyle Strand

1

Sanırım, bir çeşit hata ayıklama modu çalıştırıyorsunuz ve VS, işaretçinizi bilinen bir konuma yeniden işaretlemeye çalışıyor, böylece daha fazla referans atma girişimi izlenip raporlanabilir. Yayın modunda aynı programı derlemeyi / çalıştırmayı deneyin.

deleteVerimlilik uğruna ve yanlış bir güvenlik fikri vermekten kaçınmak için işaretçiler genellikle içeride değiştirilmez . Silme işaretçisinin önceden tanımlanmış bir değere ayarlanması, çoğu karmaşık senaryoda işe yaramaz, çünkü silinen işaretçi muhtemelen bu konumu gösteren birkaç işaretten yalnızca biri olacaktır.

İşin aslı, bunun hakkında ne kadar çok düşünürsem, bunu yaparken her zamanki gibi VS'nin hatalı olduğunu o kadar çok görüyorum. Ya işaretçi const ise? Yine de değiştirecek mi?


Evet, sabit işaretçiler bile bu gizemli 8123'e yönlendiriliyor!
tjwrona1992

VS'ye bir taş daha var :) Daha bu sabah birisi neden VS yerine g ++ kullanmaları gerektiğini sordu. İşte gidiyor.
SergeyA

7
@SergeyA ancak işaretçi silinen bir işaretçi DEREF çalıştı segfault tarafından size gösterecektir silinen diğer tarafı dereffing gelen ve NULL olarak eşit olmayacaktır. Diğer durumda, yalnızca sayfa da serbest bırakılırsa çökecektir (ki bu pek olası değildir). Daha hızlı başarısız olun; daha erken çöz.
cırcır ucube

1
@ratchetfreak "Hızlı başarısız ol, daha erken çöz" çok değerli bir mantradır, ancak "Anahtar adli kanıtları yok ederek hızlı başarısız olun" bu kadar değerli bir mantra başlatmaz. Basit durumlarda, uygun olabilir, ancak daha karmaşık durumlarda (en çok yardıma ihtiyaç duyduğumuz durumlarda), değerli bilgileri silmek, sorunu çözmek için mevcut araçlarımı azaltır.
Cort Ammon

2
@ tjwrona1992: Microsoft, bence burada doğru olanı yapıyor. Bir işaretçiyi temizlemek, hiç yapmamaktan daha iyidir. Ve bu size hata ayıklamada bir soruna neden oluyorsa, hatalı silme çağrısından önce bir kesme noktası koyun. Muhtemelen, böyle bir şey olmadan sorunu asla fark edemezsiniz. Ve bu hataları bulmak için daha iyi bir çözümünüz varsa, onu kullanın ve Microsoft'un ne yaptığını neden önemsiyorsunuz?
Zan Lynx

0

İşaretçiyi sildikten sonra işaret ettiği bellek hala geçerli olabilir. Bu hatayı tezahür ettirmek için, işaretçi değeri belirgin bir değere ayarlanır. Bu, hata ayıklama sürecine gerçekten yardımcı olur. Değer olarak ayarlanmışsa NULL, program akışında asla potansiyel hata olarak görünmeyebilir. Bu nedenle, daha sonra test ettiğinizde bir hatayı gizleyebilirNULL .

Bir başka nokta da, bazı çalışma zamanı iyileştiricilerinin bu değeri kontrol edip sonuçlarını değiştirebilmesidir.

Daha önceki zamanlarda MS değeri olarak ayarladı 0xcfffffff.

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.