Bir boşluk işaretçisini silmek güvenli midir?


96

Aşağıdaki koda sahip olduğumu varsayalım:

void* my_alloc (size_t size)
{
   return new char [size];
}

void my_free (void* ptr)
{
   delete [] ptr;
}

Bu güvenli mi? Yoksa silme ptrişleminden char*önce atılması mı gerekiyor?


Hafıza yönetimini neden kendiniz yapıyorsunuz? Hangi veri yapısını oluşturuyorsunuz? Açık bellek yönetimi yapma ihtiyacı C ++ 'da oldukça nadirdir; genellikle STL'den (veya bir tutamda Boost'tan) sizin için işleyen sınıfları kullanmalısınız.
Brian

6
Sadece okuyan insanlar için, win c ++ 'daki iş parçacıklarım için parametre olarak void * değişkenlerini kullanıyorum (bakınız _beginthreadex). Genellikle keskin bir şekilde sınıfları işaret ederler.
ryansstack

4
Bu durumda, tahsis izleme istatistikleri veya optimize edilmiş bir bellek havuzu içerebilen, yeni / silme için genel amaçlı bir sarmalayıcıdır. Diğer durumlarda, nesne işaretçilerinin yanlış bir şekilde geçersiz * üye değişkenleri olarak depolandığını ve yıkıcıda uygun nesne türüne geri dönmeden yanlış bir şekilde silindiğini gördüm. Bu yüzden güvenlik / tuzakları merak ettim.
An̲̳̳drew

2
Yeni / sil için genel amaçlı bir sarmalayıcı için, yeni / sil operatörlerini aşırı yükleyebilirsiniz. Hangi ortamı kullandığınıza bağlı olarak, muhtemelen ayırmaları izlemek için bellek yönetimine kancalar alırsınız. Neyi sildiğinizi bilmediğiniz bir durumla karşılaşırsanız, tasarımınızın yetersiz olduğuna ve yeniden düzenleme gerektirdiğine dair güçlü bir ipucu olarak alın.
Hans

41
Bence soruyu cevaplamak yerine sorgulamak çok fazla. (Sadece burada değil, tüm SO)
Petruza

Yanıtlar:


24

"Güvenli" e bağlıdır. Genellikle işe yarayacaktır çünkü bilgi, tahsisin kendisi hakkında gösterici ile birlikte depolanır, böylece ayırıcı onu doğru yere döndürebilir. Bu anlamda, ayırıcınız dahili sınır etiketleri kullandığı sürece "güvenlidir". (Çoğu yapar.)

Ancak, diğer yanıtlarda da belirtildiği gibi, bir geçersiz göstericinin silinmesi yıkıcıları çağırmayacaktır, bu da bir problem olabilir. Bu anlamda "güvenli" değil.

Yaptığın şeyi, yaptığın şekilde yapmak için iyi bir sebep yok. Kendi serbest bırakma işlevlerinizi yazmak istiyorsanız, doğru türde işlevler oluşturmak için işlev şablonlarını kullanabilirsiniz. Bunu yapmanın iyi bir nedeni, belirli türler için son derece verimli olabilen havuz ayırıcılar oluşturmaktır.

Diğer yanıtlarda da belirtildiği gibi, bu C ++ ' da tanımlanmamış bir davranıştır . Genel olarak, konunun kendisi karmaşık ve çelişkili fikirlerle dolu olmasına rağmen, tanımlanmamış davranışlardan kaçınmak iyidir.


9
Bu nasıl kabul edilen bir cevap? "Bir boşluk işaretçisini silmek" mantıklı değildir - güvenlik tartışmalı bir noktadır.
Kerrek SB

6
"Yaptığınız şeyi yaptığınız gibi yapmak için iyi bir neden yok." Bu senin fikrin, gerçek değil.
rxantos

8
@rxantos Sorunun yazarının yapmak istediğini yapmanın C ++ 'da iyi bir fikir olduğu bir karşı örnek sağlayın.
Christopher

Bu sorunun cevabı aslında çoğunlukla makul olduğunu düşünüyorum, ama bir de bu soru için herhangi bir cevap olduğunu düşünüyorum ihtiyaçları en azından söz bu tanımsız davranış olduğunu.
Kyle Strand

@Christopher Türe özgü olmayan ancak basitçe çalışan tek bir çöp toplama sistemi yazmayı deneyin. Gerçek şu ki sizeof(T*) == sizeof(U*)tüm T,Uşablon bir 1 olmıyanları mümkün olmalıdır düşündürmektedir void *tabanlı Çöp toplayıcı uygulaması. Ama sonra, gc aslında bir işaretçiyi silmek / serbest bırakmak zorunda kaldığında tam olarak bu soru ortaya çıkar. Çalışmasını sağlamak için ya lambda işlevi yıkıcı sarmalayıcılara (urgh) ihtiyacınız var ya da bir tür ile depolanabilir bir şey arasında gidip gelmeye izin veren bir tür dinamik "veri olarak tür" türünde bir şeye ihtiyacınız olacak.
BitTickler

148

Boş gösterici aracılığıyla silme, C ++ Standardı tarafından tanımlanmamıştır - bkz. Bölüm 5.3.5 / 3:

İlk alternatifte (nesneyi sil), işlenenin statik türü dinamik türünden farklıysa, statik tür, işlenenin dinamik türünün temel bir sınıfı olmalıdır ve statik tür, sanal bir yıkıcıya sahip olmalıdır veya davranış tanımsız . İkinci alternatifte (dizi sil), silinecek nesnenin dinamik türü statik türünden farklıysa, davranış tanımsızdır.

Ve dipnotu:

Bu, void * türünde bir işaretçi kullanılarak bir nesnenin silinemeyeceği anlamına gelir çünkü void türünde nesne yoktur

.


6
Doğru alıntıya bastığınızdan emin misiniz? Dipnotun şu metne atıfta bulunduğunu düşünüyorum: "İlk alternatifte (nesneyi sil), işlenenin statik türü dinamik türünden farklıysa, statik tür, işlenenin dinamik türünün ve statik türünün bir temel sınıfı olacaktır. tür sanal bir yıkıcıya sahip olmalıdır veya davranış tanımsızdır. İkinci alternatifte (dizi sil), silinecek nesnenin dinamik türü statik türünden farklıysa, davranış tanımsızdır. " :)
Johannes Schaub - litb

2
Haklısın - cevabı güncelledim. Yine de temel noktayı olumsuzladığını düşünmüyorum?

Hayır tabii değil. Hala UB olduğunu söylüyor. Dahası, artık normal bir şekilde boşluğu * silmenin UB olduğunu belirtiyor :)
Johannes Schaub - litb

Boş göstericinin işaretli adres belleğini NULLuygulama bellek yönetimi için herhangi bir fark yaratacak şekilde doldurun mu?
artu-hnrq

25

Bu iyi bir fikir değil ve C ++ 'da yapacağınız bir şey değil. Tür bilgilerinizi sebepsiz yere kaybediyorsunuz.

Yıkıcınız, dizinizdeki sildiğiniz nesnelere, onu ilkel olmayan türler için çağırdığınızda çağrılmaz.

Bunun yerine yeni / sil'i geçersiz kılmalısınız.

Boşluğu * silmek muhtemelen hafızanızı şans eseri doğru bir şekilde boşaltacaktır, ancak bu yanlıştır çünkü sonuçlar tanımsızdır.

Eğer bilmediğim bir sebepten dolayı işaretçinizi bir boşlukta * saklamanız gerekiyorsa, o zaman serbest bırakın, malloc ve bedava kullanmalısınız.


5
Yıkıcının çağrılmaması konusunda haklısın, ama boyutunun bilinmemesi konusunda yanılıyorsun. Eğer yeni aldığım bir işaretçi silmek verirsek yok aslında tamamen ayrı türünden, bir şey boyutu silinmesini biliyorum. Nasıl yapılır C ++ standardı tarafından belirtilmemiştir, ancak işaretçinin 'new' ile döndürdüğü verilerden hemen önce boyutun depolandığı uygulamalar gördüm.
KeyserSoze

C ++ standardı tanımsız olduğunu söylese de boyutla ilgili kısım kaldırıldı. Malloc / free'nin void * işaretçileri için işe yarayacağını biliyorum.
Brian R. Bondy

Standardın ilgili bölümüne bir web bağlantınız olduğunu düşünmüyor musunuz? Baktığım birkaç yeni / sil uygulamasının kesinlikle tür bilgisi olmadan doğru çalıştığını biliyorum, ancak kabul ediyorum, standardın neyi belirttiğine bakmadım. IIRC C ++ başlangıçta dizileri silerken bir dizi öğesi sayısını gerektiriyordu, ancak artık en yeni sürümlerde bunu yapmıyordu.
KeyserSoze

Lütfen @Neil Butterworth cevabına bakın. Bence cevabı kabul edilmiş olmalıdır.
Brian R. Bondy

2
@keysersoze: Genel olarak ifadenize katılmıyorum. Bazı uygulamaların bellek tahsis edilmeden önce boyutu depolaması, bunun bir kural olduğu anlamına gelmez.

13

Bir boşluk işaretçisini silmek tehlikelidir çünkü yıkıcılar gerçekte işaret ettikleri değere göre çağrılmazlar. Bu, uygulamanızda bellek / kaynak sızıntılarına neden olabilir.


2
char'ın kurucusu / yıkıcısı yok.
rxantos

9

Soru hiçbir anlam ifade etmiyor. Kafanızın karışması kısmen, insanların sıklıkla kullandığı özensiz dilden kaynaklanıyor olabilir delete:

Dinamik olarak tahsis edilmiş deletebir nesneyi yok etmek için kullanırsınız . Bunu yapın, o nesneye bir işaretçi ile bir silme ifadesi oluşturursunuz . Asla "işaretçiyi silemezsiniz". Gerçekte yaptığınız şey "adresiyle tanımlanan bir nesneyi silmektir".

Şimdi sorunun neden anlamsız olduğunu görüyoruz: Boş işaretçi "bir nesnenin adresi" değildir. Bu, herhangi bir anlam bilgisi olmayan bir adres. Bu olabilir , gerçek bir nesnenin adresinden gelmiş, ancak kodlanmış çünkü bu bilgiler, kaybolur tip orijinal işaretçisi. Bir nesne işaretçisini geri yüklemenin tek yolu, boşluk işaretçisini bir nesne işaretçisine geri döndürmektir (bu, yazarın işaretçinin ne anlama geldiğini bilmesini gerektirir). voidkendisi eksik bir türdür ve bu nedenle asla bir nesnenin türü değildir ve bir nesneyi tanımlamak için asla bir boş gösterici kullanılamaz. (Nesneler, türleri ve adresleri ile birlikte tanımlanır.)


Kuşkusuz, soru herhangi bir çevreleyen bağlam olmadan pek bir anlam ifade etmiyor. Bazı C ++ derleyicileri yine de bu tür anlamsız kodları mutlu bir şekilde derler (eğer yardımcı hissediyorlarsa, bu konuda bir uyarıda bulunabilirler). Bu nedenle, bu tavsiye edilmeyen işlemi içeren eski kodu çalıştırmanın bilinen risklerini değerlendirmek için soru soruldu: çökecek mi? karakter dizisi belleğinin bir kısmını veya tamamını sızdırıyor mu? platforma özgü başka bir şey?
An̲̳̳drew

Düşünceli yanıtınız için teşekkürler. Olumlu oylama!
An̲̳̳drew

1
@Andrew: Korkarım bu konuda standart oldukça açık: "İşlenenin değeri deletebir boş işaretçi değeri, önceki bir yeni ifade tarafından oluşturulan dizi olmayan bir nesneye bir işaretçi veya bir Bu tür bir nesnenin temel sınıfını temsil eden alt nesne. Değilse, davranış tanımsızdır. " Yani bir derleyici kodunuzu teşhis olmadan kabul ederse, bu derleyicideki bir hatadan başka bir şey değildir ...
Kerrek SB

1
@KerrekSB - Derleyicideki bir hatadan başka bir şey değil - Katılmıyorum. Standart, davranışın tanımsız olduğunu söylüyor. Bu, derleyicinin / uygulamanın her şeyi yapabileceği ve yine de standartla uyumlu olabileceği anlamına gelir. Derleyicinin yanıtı bir void * işaretçisini silemeyeceğinizi söylemekse, sorun değil. Derleyicinin yanıtı sabit sürücüyü silmekse, bu da sorun değil. OTOH, derleyicinin yanıtı herhangi bir teşhis üretmeyecekse, bunun yerine o işaretçiyle ilişkili belleği serbest bırakan kod üretecekse, bu da sorun değil. Bu, UB'nin bu biçimiyle başa çıkmanın basit bir yoludur.
David Hammen

Sadece eklemek için: kullanımına göz yummuyorum delete void_pointer. Tanımlanmamış bir davranış. Yanıt programcının yapmak istediği şeyi yapıyor gibi görünse bile, programcılar hiçbir zaman tanımlanmamış davranışa başvurmamalıdır.
David Hammen

7

Bunu gerçekten yapmanız gerekiyorsa, neden aracı kişiyi ( newve deleteoperatörleri) kesip küresel operator newve operator deletedoğrudan aramıyorsunuz ? (Elbette, newve deleteoperatörlerini ayarlamaya çalışıyorsanız , aslında yeniden uygulamalısınız operator newve operator delete.)

void* my_alloc (size_t size)
{
   return ::operator new(size);
}

void my_free (void* ptr)
{
   ::operator delete(ptr);
}

Unutmayın, aksine malloc(), başarısızlık üzerine operator newfırlatır std::bad_alloc(veya new_handlereğer kayıtlıysa çağırır ).


Char bir kurucu / yıkıcıya sahip olmadığı için bu doğrudur.
rxantos

5

Çünkü karakterin özel bir yıkıcı mantığı yoktur. BU işe yaramayacak.

class foo
{
   ~foo() { printf("huzza"); }
}

main()
{
   foo * myFoo = new foo();
   delete ((void*)foo);
}

D'ctor aranmayacak.


5

Void * kullanmak istiyorsanız, neden sadece malloc / free kullanmıyorsunuz? new / delete, bellek yönetiminden daha fazlasıdır. Temel olarak, new / delete bir kurucu / yıkıcı çağırır ve daha fazla şey olur. Yalnızca yerleşik türleri (char * gibi) kullanırsanız ve bunları void * aracılığıyla silerseniz, işe yarayacaktır, ancak yine de önerilmez. Sonuç olarak, void * kullanmak istiyorsanız malloc / free kullanın. Aksi takdirde, rahatınız için şablon işlevlerini kullanabilirsiniz.

template<typename T>
T* my_alloc (size_t size)
{
   return new T [size];
}

template<typename T>
void my_free (T* ptr)
{
   delete [] ptr;
}

int main(void)
{
    char* pChar = my_alloc<char>(10);
    my_free(pChar);
}

Örnekteki kodu yazmadım - bu modelin birkaç yerde kullanıldığını, C / C ++ bellek yönetimini merakla karıştırdığını ve belirli tehlikelerin neler olduğunu merak ettim.
An̲̳̳drew

C / C ++ yazmak başarısızlığın reçetesidir. Bunu kim yazdıysa, birini ya da diğerini yazmış olmalı.
David Thornley

2
@David Bu C ++, C / C ++ değil. C'nin şablonları yoktur, yeni ve sil kullanmaz.
rxantos

5

Bir çok insan zaten hayır, bir boşluk işaretçisini silmenin güvenli olmadığını söyleyerek yorum yaptı. Buna katılıyorum, ancak bitişik dizileri veya benzer bir şeyi tahsis etmek için boşluk işaretçileriyle çalışıyorsanız, bunu güvenle newkullanabilmeniz için bunu yapabileceğinizi de eklemek istedim delete(ahem ile , biraz fazla iş). Bu, bellek bölgesine ('arena' olarak adlandırılır) bir boşluk gösterici tahsis ederek ve ardından göstericiyi arenaya yeniye sunarak yapılır. C ++ SSS'de bu bölüme bakın . Bu, C ++ 'da bellek havuzlarını uygulamaya yönelik yaygın bir yaklaşımdır.


0

Bunu yapmak için neredeyse hiçbir neden yok.

Her şeyden önce, sen bilmiyorsan türünü verilerinin ve tanıdığınız tüm bu olmasıdır void*o zaman gerçekten sadece bir typeless olarak bu verileri tedavi edilmelidir, damla ikili veri (bir unsigned char*) ve kullanım malloc/ freebaşa . Bu bazen dalga formu verileri ve benzerleri gibi, void*işaretçileri C apis'e iletmeniz gereken şeyler için gereklidir . Bu iyi.

Eğer varsa yapmak verinin türünü (yani bir ctor / dtor vardır) biliyorum, ama nedense bir ile sona erdi void*(herhangi bir nedenle var) pointer o zaman gerçekten türüne geri kullanması gereklidir sen bunu biliyorsun ol ve deleteonu çağır .


0

Kod yansıtma ve diğer belirsizlik özellikleri için çerçevemde void *, (bilinmeyen türler) kullandım ve şu ana kadar herhangi bir derleyiciden herhangi bir sorun (bellek sızıntısı, erişim ihlalleri vb.) Yaşamadım. Yalnızca işlemin standart olmaması nedeniyle uyarılar.

Bilinmeyen bir (boşluğu *) silmek tamamen mantıklıdır. İmlecin bu yönergeleri izlediğinden emin olun, aksi takdirde anlam ifade etmeyebilir:

1) Bilinmeyen işaretçi, önemsiz bir yapısökücüsü olan bir türe işaret etmemelidir ve bu nedenle bilinmeyen bir işaretçi olarak kullanıldığında, ASLA SİLİNMEMELİDİR. Bilinmeyen işaretçiyi yalnızca ORİJİNAL türe geri çevirdikten SONRA silin.

2) Örneğe yığın bağlantılı veya yığın bağlantılı bellekte bilinmeyen bir işaretçi olarak mı başvuruluyor? Bilinmeyen işaretçi yığındaki bir örneğe başvuruyorsa, ASLA SİLİNMEMELİDİR!

3) Bilinmeyen işaretçinin geçerli bir bellek bölgesi olduğundan% 100 emin misiniz? Hayır, o zaman ASLA DELTEDİLMEMELİ!

Genel olarak, bilinmeyen (void *) bir işaretçi türü kullanılarak yapılabilecek çok az doğrudan iş vardır. Bununla birlikte, dolaylı olarak void *, C ++ geliştiricilerinin veri belirsizliği gerektiğinde güvenebilecekleri harika bir varlıktır.


0

Sadece bir arabellek istiyorsanız, malloc / free kullanın. New / delete kullanmanız gerekiyorsa, önemsiz bir sarmalayıcı sınıfını düşünün:

template<int size_ > struct size_buffer { 
  char data_[ size_]; 
  operator void*() { return (void*)&data_; }
};

typedef sized_buffer<100> OpaqueBuffer; // logical description of your sized buffer

OpaqueBuffer* ptr = new OpaqueBuffer();

delete ptr;

0

Özel char durumu için.

char, özel bir yıkıcıya sahip olmayan içsel bir türdür. Yani sızıntı argümanları tartışmalı bir konudur.

sizeof (char) genellikle bir olduğundan, hizalama argümanı da yoktur. Sizeof (char) 'un bir olmadığı nadir platformlarda, karakterleri için yeterince hizalanmış bellek ayırırlar. Dolayısıyla hizalama argümanı da tartışmalı bir argümandır.

malloc / free bu durumda daha hızlı olacaktır. Ancak std :: bad_alloc'u kaybedersiniz ve malloc'un sonucunu kontrol etmeniz gerekir. Global yeni ve silme operatörlerini çağırmak, aracıyı atladığı için daha iyi olabilir.


1
" sizeof (char) genellikle birdir " sizeof (char) HER ZAMAN birdir
wonderguy

Yakın zamana kadar (2019) insanlar bunun newatmak için tanımlandığını düşünmüyorlar . Bu doğru değil. Derleyici ve derleyici anahtarına bağlıdır. Örneğin MSVC2019 /GX[-] enable C++ EH (same as /EHsc)anahtarlarına bakın . Ayrıca gömülü sistemlerde çoğu, C ++ istisnaları için performans vergilerini ödememeyi tercih eder. Yani "Ama std :: bad_alloc ..." ile başlayan cümle sorgulanabilir.
BitTickler
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.