Geçerlilik için test işaretçileri (C / C ++)


91

Belirli bir göstericinin "geçerli" olup olmadığını belirlemenin (programlı olarak) herhangi bir yolu var mı? NULL'u kontrol etmek kolaydır, ancak 0x00001234 gibi şeyler ne olacak? Bu tür bir göstericinin referansını kaldırmaya çalışırken bir istisna / çökme meydana gelir.

Çapraz platform yöntemi tercih edilir, ancak platforma özgü (Windows ve Linux için) de uygundur.

Açıklama için güncelleme: Sorun eski / serbest / başlatılmamış işaretçilerde değil; bunun yerine, çağırandan işaretçiler alan bir API uyguluyorum (bir dizeye işaretçi, dosya tanıtıcısı, vb. gibi). Arayan, işaretçi olarak (kasıtlı olarak veya yanlışlıkla) geçersiz bir değer gönderebilir. Bir çökmeyi nasıl önleyebilirim?



Sanırım linux için en olumlu cevabı George Carrette veriyor. Bu yeterli değilse, işlev sembolü tablosunu kitaplığa veya kendi işlev tablolarına sahip mevcut kitaplıkların başka bir düzey tablosuna oluşturmayı düşünün. Sonra bu kesin tablolarla karşılaştır. Elbette, bu olumsuz yanıtlar da doğrudur: Kullanıcı uygulamasına birçok ek kısıtlama getirmedikçe bir işlev işaretçisinin geçerli olup olmadığından% 100 emin olamazsınız.
minghua

API Spesifikasyonu, uygulama tarafından yerine getirilmesi gereken böyle bir yükümlülüğü gerçekten belirtiyor mu? Bu arada, hem geliştirici hem de tasarımcı olduğunuz varsayılmamış gibi davranıyorum. Demek istediğim, bir API'nin "Bir argüman olarak geçersiz bir gösterici iletilmesi durumunda, fonksiyon sorunu halletmeli ve NULL döndürmelidir." Gibi bir şey belirteceğini düşünmüyorum. Bir API, bir hizmeti hacklerle değil, uygun kullanım koşulları altında sağlama yükümlülüğünü üstlenir. Yine de biraz aptal olmaktan zarar gelmez. Bir referans kullanmak, bu tür vakaların daha az hasara yol açmasını sağlar. :)
Poniros

Yanıtlar:


75

Açıklama için güncelleme: Sorun eski, serbest bırakılmış veya başlatılmamış işaretçilerde değildir; bunun yerine, çağırandan işaretçileri alan bir API uyguluyorum (bir dizeye işaretçi, dosya tanıtıcısı vb. gibi). Arayan, işaretçi olarak (kasıtlı olarak veya yanlışlıkla) geçersiz bir değer gönderebilir. Bir çökmeyi nasıl önleyebilirim?

Bu kontrolü yapamazsınız. Bir işaretçinin "geçerli" olup olmadığını kontrol etmenin hiçbir yolu yoktur. İnsanlar bir işaretçi kullanan bir işlevi kullandıklarında, bu insanların ne yaptıklarını bildiklerine güvenmelisiniz. İşaretçi değeri olarak size 0x4211'i iletirlerse, bunun 0x4211 adresine işaret ettiğine güvenmeniz gerekir. Ve bir nesneye "kazara" vururlarsa, o zaman bazı korkutucu işletim sistemi işlevlerini (IsValidPtr veya her neyse) kullansanız bile, yine de bir hataya düşer ve hızlı bir şekilde başarısız olmazsınız.

Bu tür şeyleri işaret etmek için boş işaretçiler kullanmaya başlayın ve kitaplığınızın kullanıcısına yanlışlıkla geçersiz işaretçiler geçirme eğilimindeyken işaretçi kullanmamalarını söyleyin, cidden :)


Muhtemelen doğru cevap budur, ancak ortak hexspeak bellek konumlarını kontrol eden basit bir işlevin genel hata ayıklama için yararlı olacağını düşünüyorum ... Şu anda bazen 0xfeeefeee'yi gösteren bir işaretçim var ve yapabileceğim basit bir işlevim olsaydı suçluyu bulmayı çok daha kolay hale getirirdi ... DÜZENLEME: Kendi başına bir tane yazmak zor olmasa da sanırım ..
kuant

@quant, sorun, bazı C ve C ++ kodlarının geçersiz bir adres üzerinde kontrol yapmadan işaretçi aritmetiği yapabilmesidir (çöp girişine, çöp çıkış ilkesine dayanarak) ve bu nedenle bunlardan birinden "aritmetik olarak değiştirilmiş" bir işaretçi geçirecektir. -bilinen geçersiz adresler. Yaygın durumlar, geçersiz bir nesne adresine veya yanlış türden birine dayalı olarak var olmayan bir vtable'dan bir yöntem aramak veya basitçe bir işaretçiden bir yapıya işaret etmeyen bir yapıya alan okumaktır.
2014

Bu, temelde yalnızca dış dünyadan dizi indeksleri alabileceğiniz anlamına gelir. Arayandan kendisini savunması gereken bir API, arayüzde işaretçiler içeremez. Bununla birlikte, işaretçilerin geçerliliği hakkındaki iddialarda (dahili olarak sahip olmanız gereken) makrolara sahip olmak yine de iyi olacaktır. Bir göstericinin başlangıç ​​noktası ve uzunluğu bilinen bir dizinin içini göstermesi garanti edilirse, bu açıkça kontrol edilebilir. Bir iddia ihlalinden (belgelenmiş hata) ölmek, bir deref'ten (belgelenmemiş hata) daha iyidir.
Rob

34

Linux altında bir C programının çalıştığı belleğin durumu hakkında iç gözlem yapmanın üç kolay yolu ve bu sorunun neden bazı bağlamlarda uygun karmaşık cevaplara sahip olduğu anlatılmaktadır.

  1. Getpagesize () çağrıldıktan ve işaretçiyi bir sayfa sınırına yuvarladıktan sonra, bir sayfanın geçerli olup olmadığını ve işlem çalışma kümesinin bir parçası olup olmadığını öğrenmek için mincore () öğesini çağırabilirsiniz. Bunun bazı çekirdek kaynakları gerektirdiğini unutmayın, bu yüzden onu karşılaştırmalı ve bu işlevi çağırmanın API'nizde gerçekten uygun olup olmadığını belirlemelisiniz. API'niz kesintileri ele alacaksa veya seri bağlantı noktalarından belleğe okuyacaksa, öngörülemeyen davranışlardan kaçınmak için bunu çağırmanız uygun olur.
  2. Kullanılabilir bir / proc / self dizini olup olmadığını belirlemek için stat () çağrıldıktan sonra, bir işaretçinin bulunduğu bölge hakkında bilgi bulmak için / proc / self / maps'ı açıp okuyabilirsiniz. İşlem bilgileri sözde dosya sistemi olan proc için man sayfasını inceleyin. Açıkçası bu nispeten pahalıdır, ancak ayrıştırmanın sonucunu ikili arama kullanarak verimli bir şekilde arayabileceğiniz bir diziye önbelleğe almaktan kurtulabilirsiniz. Ayrıca / proc / self / smaps'i de göz önünde bulundurun. API'niz yüksek performanslı bilgi işlem içinse, program, tek tip olmayan bellek mimarisi olan numa için man sayfası altında belgelenen / proc / self / numa hakkında bilgi edinmek isteyecektir.
  3. Get_mempolicy (MPOL_F_ADDR) çağrısı, birden fazla yürütme iş parçacığının olduğu ve işinizi cpu çekirdekleriyle ve soket kaynaklarıyla ilgili olduğu için tek tip olmayan bellek için yakınlığa sahip olacak şekilde yönettiğiniz yüksek performanslı bilgi işlem api çalışması için uygundur. Böyle bir api elbette size bir göstericinin geçerli olup olmadığını da söyleyecektir.

Microsoft Windows altında, İşlem Durumu API'si altında (ayrıca NUMA API'sinde) belgelenen QueryWorkingSetEx işlevi vardır. Karmaşık NUMA API programlamasının bir sonucu olarak, bu işlev aynı zamanda basit "geçerlilik için test işaretçileri (C / C ++)" çalışması yapmanıza da izin verecektir, bu nedenle en az 15 yıldır kullanımdan kaldırılma olasılığı düşüktür.


13
Sorunun kendisi hakkında ahlaki olmaya çalışmayan ve aslında ona mükemmel cevap veren ilk cevap. İnsanlar bazen, örneğin üçüncü taraf kitaplıklardaki veya eski koddaki hataları bulmak için gerçekten bu tür bir hata ayıklama yaklaşımına ihtiyaç duyduğunun farkına varmazlar çünkü valgrind bile yalnızca bunlara erişirken vahşi işaretçileri bulur, örneğin geçerlilik için işaretçileri düzenli olarak kontrol etmek istiyorsanız değil kodunuzda başka bir yerden üzerine yazılmış bir önbellek tablosunda ...
lumpidu

Kabul edilen cevap bu olmalıdır. Ben de aynı şekilde Linux olmayan bir platformda yaptım. Temelde, süreç bilgisini sürecin kendisine ifşa ediyor. Bu yönüyle, pencerelerin işlem durumu API'si aracılığıyla daha anlamlı bilgileri açığa çıkararak linux'tan daha iyi bir iş çıkardığı görülmektedir.
minghua

31

Arayanın geçersiz bir işaretçi göndermesinin neden olduğu bir çökmeyi önlemek, bulunması zor olan sessiz hatalar yapmanın iyi bir yoludur.

API'nizi kullanan programcının, kodunun gizlemek yerine çökerek sahte olduğuna dair net bir mesaj alması daha iyi değil mi?


8
Gerçi bazı durumlarda, API denir hemen kötü bir pointer kontrol olduğunu erken başarısız nasıl. Örneğin, API işaretçiyi yalnızca daha sonra erteleneceği bir veri yapısında saklarsa ne olur? Daha sonra, API'yi kötü bir işaretçi geçirmek, daha sonraki bir noktada rastgele bir çökmeye neden olacaktır. Bu durumda, hatalı değerin başlangıçta tanıtıldığı API çağrısında daha erken başarısız olmak daha iyi olacaktır.
peterflynn

28

Win32 / 64'te bunu yapmanın bir yolu var. İşaretçiyi okumaya çalışın ve başarısızlık üzerine fırlatılacak sonuçta ortaya çıkan SEH yürütmesini yakala. Atmazsa, geçerli bir işaretçi.

Yine de bu yöntemle ilgili sorun, işaretçiden verileri okuyup okuyamayacağınızı döndürmesidir. Tip güvenliği veya herhangi bir sayıdaki diğer değişmezler hakkında hiçbir garanti vermez. Genel olarak bu yöntem, "evet, şu anda geçmiş bir zamanda bellekteki o belirli yeri okuyabilirim" demekten başka bir şey için iyidir.

Kısacası, bunu yapmayın;)

Raymond Chen'in bu konuda bir blog yazısı var: http://blogs.msdn.com/oldnewthing/archive/2007/06/25/3507294.aspx


3
@Tim, bunu C ++ 'da yapmanın bir yolu yok.
JaredPar

6
"Geçerli işaretçi" yi "erişim ihlaline / segfault'a neden olmaz" olarak tanımlarsanız, bu yalnızca "doğru yanıttır". Ben bunu "kullanacağınız amaç için tahsis edilmiş anlamlı verilere işaret eden" olarak tanımlamayı tercih ederim. Bunun işaretçi geçerliliğinin daha iyi bir tanımı olduğunu iddia ediyorum ...;)
jalf

İşaretçi geçerli olsa bile bu şekilde kontrol edilemez. Thread1 () 'i düşünün {.. if (IsValidPtr (p)) * p = 7; ...} iş parçacığı2 () {uyku (1); p sil; ...}
Christopher

2
@Christopher, çok doğru. "Şu anda geçen bir zamanda bellekteki o belirli yeri okuyabilirim"
demeliydim

@JaredPar: Gerçekten Kötü bir öneri. Bir koruma sayfasını tetikleyebilir, böylece yığın daha sonra genişletilmez veya eşit derecede güzel bir şey olmaz.
Deduplicator

16

AFAIK yolu yok. Belleği boşalttıktan sonra her zaman işaretçileri NULL olarak ayarlayarak bu durumdan kaçınmaya çalışmalısınız.


4
Bir işaretçiyi boş olarak ayarlamak yanlış bir güvenlik duygusu dışında size hiçbir şey vermez.

Bu doğru değil. Özellikle C ++ 'da null olup olmadığını kontrol ederek üye nesnelerin silinip silinmeyeceğini belirleyebilirsiniz. Ayrıca, C ++ 'da boş işaretçileri silmenin geçerli olduğunu, bu nedenle yıkıcılardaki nesneleri koşulsuz olarak silmenin popüler olduğunu unutmayın.
Ferdinand Beyer

4
int * p = yeni int (0); int * p2 = p; p sil; p = NULL; p2'yi sil; // crash

1
zabzonk ve ?? onun söylediği, boş bir işaretçiyi silebileceğinizdir. p2 bir boş gösterici değildir, ancak geçersiz bir göstericidir. daha önce null olarak ayarlamalısınız.
Johannes Schaub -

2
Bellek için gösterilen takma adlarınız varsa, bunlardan yalnızca biri NULL olarak ayarlanır, diğer adlar ortalıkta sarkar.
jdehaan

7

Buna ve bu soruya bir göz atın . Ayrıca akıllı işaretleyicilere bir göz atın .


7

Bu konudaki cevapla ilgili olarak biraz yukarı gelince:

Windows için IsBadReadPtr (), IsBadWritePtr (), IsBadCodePtr (), IsBadStringPtr ().

Benim tavsiyem onlardan uzak durmaktır, birisi bunu zaten yayınladı: http://blogs.msdn.com/oldnewthing/archive/2007/06/25/3507294.aspx

Aynı konuyla ilgili ve aynı yazara ait başka bir gönderi (sanırım) şudur: http://blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx ("IsBadXxxPtr gerçekten CrashProgramRandomly olarak adlandırılmalıdır ").

API'nizin kullanıcıları kötü veri gönderirse çökmesine izin verin. Sorun, aktarılan verilerin daha sonraya kadar kullanılmaması ise (ve bu, nedenini bulmayı zorlaştırıyorsa), girişte dizelerin vb. Günlüğe kaydedildiği bir hata ayıklama modu ekleyin. Eğer kötülerse, bariz olacaktır (ve muhtemelen çökecektir). Sık sık oluyorsa, API'nizi işlemden çıkarmaya ve ana işlem yerine API sürecini çökertmelerine izin vermeye değer olabilir.


Muhtemelen başka bir yol, _CrtIsValidHeapPointer'ı kullanmaktır . İşaretçi geçerliyse bu işlev TRUE döndürür ve işaretçi serbest bırakıldığında bir istisna atar. Belgelendiği gibi, bu işlev yalnızca CRT hata ayıklamasında kullanılabilir.
Crend King

6

İlk olarak, kendinizi arayan kişiden kasıtlı olarak bir çökmeye neden olmaya çalışmaktan korumaya çalışmanın bir anlamı yok. Bunu, geçersiz bir işaretçiyle kendileri erişmeye çalışarak kolayca yapabilirler. Diğer birçok yol var - sadece hafızanızın veya yığının üzerine yazabilirler. Bu tür şeylere karşı korunmanız gerekiyorsa, iletişim için soketler veya başka bir IPC kullanarak ayrı bir işlemde çalıştırmanız gerekir.

Ortakların / müşterilerin / kullanıcıların işlevselliği genişletmelerine olanak tanıyan oldukça fazla yazılım yazıyoruz. Kaçınılmaz olarak herhangi bir hata önce bize bildirilir, bu nedenle sorunun eklenti kodunda olduğunu kolayca gösterebilmek yararlıdır. Ek olarak güvenlik endişeleri vardır ve bazı kullanıcılar diğerlerinden daha güvenilirdir.

Performans / üretim gereksinimleri ve güvenilirliğe bağlı olarak bir dizi farklı yöntem kullanıyoruz. En çok tercih edilen:

  • soketleri kullanarak ayrı işlemler (genellikle verileri metin olarak iletme).

  • paylaşılan hafızayı kullanarak işlemleri ayırın (eğer büyük miktarda veri aktarılacaksa).

  • aynı süreç mesaj kuyruğu aracılığıyla ayrı iş parçacıkları (sık sık kısa mesajlar varsa).

  • aynı işlem, bir bellek havuzundan ayrılan tüm aktarılan verileri ayrı iş parçacıkları.

  • doğrudan prosedür çağrısı yoluyla aynı süreç - bir bellek havuzundan tahsis edilen tüm aktarılan veriler.

Üçüncü taraf yazılımlarla uğraşırken yapmaya çalıştığınız şeye asla başvurmamaya çalışıyoruz - özellikle de eklentiler / kitaplık bize kaynak kodu yerine ikili olarak verildiğinde.

Bellek havuzunun kullanımı çoğu durumda oldukça kolaydır ve verimsiz olması gerekmez. Verileri ilk etapta tahsis ederseniz, işaretçileri ayırdığınız değerlerle karşılaştırmak önemsizdir. Ayrıca, ayrılan uzunluğu saklayabilir ve geçerli veri türünü ve veri aşımlarını kontrol etmek için veriden önce ve sonra "sihirli" değerler ekleyebilirsiniz.


5

Unix'te, işaretçi denetimi yapan ve EFAULT döndüren bir çekirdek sistem çağrısı kullanabilmeniz gerekir, örneğin:

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdbool.h>

bool isPointerBad( void * p )
{
   int fh = open( p, 0, 0 );
   int e = errno;

   if ( -1 == fh && e == EFAULT )
   {
      printf( "bad pointer: %p\n", p );
      return true;
   }
   else if ( fh != -1 )
   {
      close( fh );
   }

   printf( "good pointer: %p\n", p );
   return false;
}

int main()
{
   int good = 4;
   isPointerBad( (void *)3 );
   isPointerBad( &good );
   isPointerBad( "/tmp/blah" );

   return 0;
}

geri dönen:

bad pointer: 0x3
good pointer: 0x7fff375fd49c
good pointer: 0x400793

Muhtemelen open () [belki access] yerine kullanmak için daha iyi bir sistem çağrısı vardır, çünkü bunun gerçek dosya oluşturma kod yoluna ve ardından bir kapatma gereksinimine yol açma ihtimali vardır.


1
Bu harika bir hack. Bellek aralıklarını doğrulamak için farklı sistem çağrıları hakkında tavsiye görmeyi çok isterim, özellikle de yan etkileri olmayacakları garanti edilebiliyorsa. Arabelleklerin okunabilir bellekte olup olmadığını test etmek için / dev / null'a yazmak için bir dosya tanımlayıcısını açık tutabilirsiniz, ancak muhtemelen daha basit çözümler vardır. Bulabildiğim en iyi şey, errno'yu kötü bir adreste 14'e veya iyi bir adreste 2'ye ayarlayacak symlink (ptr, ""), ancak çekirdek değişiklikleri doğrulama sırasını değiştirebilir.
Preston

1
@Preston DB2'de unistd.h'nin erişimini () kullandığımızı düşünüyorum. Yukarıda open () kullandım çünkü biraz daha az anlaşılmaz, ama muhtemelen haklısınız, kullanabileceğiniz pek çok olası sistem çağrısı var. Windows önceden açık bir işaretçi kontrol API'sine sahipti, ancak iş parçacığı güvenli olmadığı ortaya çıktı (Sanırım, yazmayı denemek ve bellek aralığının sınırlarını geri yüklemek için SEH kullandı.)
Peeter Joot

4

Ben de neredeyse aynı konumda olduğum için sorunuza çok sempati duyuyorum. Ben cevapların çok ne söylediğini takdir ve Doğru olduklarından - işaretçi temin rutin olmalıdır geçerli bir işaretçi temin edilebilir. Benim durumumda, işaretçiyi bozmuş olabilecekleri neredeyse düşünülemez - ancak başarmış olsalardı , çökecek olan BENİM yazılımım ve suçu ben üstlenecek olan :-(

Benim ihtiyacım, bir bölümleme hatasından sonra devam etmem değil - bu tehlikeli olabilir - sadece müşteriye ne olduğunu sonlandırmadan önce rapor etmek istiyorum, böylece beni suçlamak yerine kodunu düzeltebilsinler!

Bunu nasıl yaptığımı buldum (Windows'ta): http://www.cplusplus.com/reference/clibrary/csignal/signal/

Bir özet vermek gerekirse:

#include <signal.h>

using namespace std;

void terminate(int param)
/// Function executed if a segmentation fault is encountered during the cast to an instance.
{
  cerr << "\nThe function received a corrupted reference - please check the user-supplied  dll.\n";
  cerr << "Terminating program...\n";
  exit(1);
}

...
void MyFunction()
{
    void (*previous_sigsegv_function)(int);
    previous_sigsegv_function = signal(SIGSEGV, terminate);

    <-- insert risky stuff here -->

    signal(SIGSEGV, previous_sigsegv_function);
}

Şimdi bu , umduğum gibi davranıyor gibi görünüyor (hata mesajını yazdırıyor, ardından programı sonlandırıyor) - ancak birisi bir kusuru fark ederse lütfen bana bildirin!


Kullanmayın exit(), RAII'yi atlatır ve bu nedenle kaynak sızıntılarına neden olabilir.
Sebastian Mach

İlginç - bu durumda düzgünce sonlandırmanın başka bir yolu var mı? Ve bunu böyle yapmanın tek sorunu çıkış ifadesi mi? "-1" aldığımı fark ettim - bu sadece "çıkış" yüzünden mi?
Mike Sadler

Hay aksi, bunun oldukça istisnai bir durum için olduğunun farkındayım. Az önce gördüm exit()ve Taşınabilir C ++ Alarm Bell'im çalmaya başladı. Bu Linux'a özgü durumda, programınızın yine de çıkacağı, gürültü için özür dilerim.
Sebastian Mach

1
sinyal (2) taşınabilir değil. Sigaction (2) kullanın. man 2 signalLinux'ta nedenini açıklayan bir paragraf var.
rptb1

1
Bu durumda genellikle exit (3) yerine abort (3) olarak adlandırırım çünkü post mortem problemini teşhis etmek için kullanabileceğiniz bir tür hata ayıklama geri izleme üretme olasılığı daha yüksektir. Çoğu Unixen'de, abort (3) çekirdeği döker (çekirdek dökümlerine izin veriliyorsa) ve Windows'ta kuruluysa bir hata ayıklayıcı başlatmayı önerir.
rptb1

2

Genel bir durum olarak bir göstericinin geçerliliğini test etmek için C ++ 'da herhangi bir hüküm yoktur. NULL (0x00000000) 'ün kötü olduğu ve çeşitli derleyicilerin ve kitaplıkların hata ayıklamayı kolaylaştırmak için burada ve orada "özel değerler" kullanmayı sevdikleri varsayılabilir (Örneğin, görsel stüdyoda 0xCECECE olarak görünen bir işaretçi görürsem, Yanlış bir şey yaptım) ama gerçek şu ki, bir işaretçi hafızanın sadece bir indeksi olduğundan, sadece işaretçiye bakarak bunun "doğru" indeks olup olmadığını söylemek neredeyse imkansızdır.

Dynamic_cast ve RTTI ile, işaret edilen nesnenin istediğiniz türde olmasını sağlamak için yapabileceğiniz çeşitli püf noktaları vardır, ancak hepsi ilk etapta geçerli bir şeye işaret etmenizi gerektirir.

Programınızın "geçersiz" işaretçileri algılayabilmesini sağlamak istiyorsanız, tavsiyem şudur: Bildirdiğiniz her işaretçiyi oluşturulduktan hemen sonra NULL veya geçerli bir adrese ayarlayın ve işaret ettiği belleği boşalttıktan hemen sonra NULL olarak ayarlayın. Bu uygulama konusunda gayret ediyorsanız, ihtiyacınız olan tek şey NULL'u kontrol etmektir.


C ++ 'da bir boş gösterici sabiti (veya bu konuda C), sabit bir integral sıfır ile temsil edilir. Pek çok uygulama, onu temsil etmek için tamamen ikili sıfırları kullanır, ancak bu güvenilecek bir şey değildir.
David Thornley

2

Bunu yapmanın taşınabilir bir yolu yoktur ve bunu belirli platformlar için yapmak zor ve imkansız arasında herhangi bir yerde olabilir. Her halükarda, asla böyle bir kontrole dayalı bir kod yazmamalısınız - en başta işaretçilerin geçersiz değerler almasına izin vermeyin.


2

İşaretçiyi kullanmadan önce ve sonra NULL olarak ayarlamak iyi bir tekniktir. Örneğin bir sınıf (bir dize) içindeki işaretçileri yönetiyorsanız bunu C ++ 'da yapmak kolaydır:

class SomeClass
{
public:
    SomeClass();
    ~SomeClass();

    void SetText( const char *text);
    char *GetText() const { return MyText; }
    void Clear();

private:
    char * MyText;
};


SomeClass::SomeClass()
{
    MyText = NULL;
}


SomeClass::~SomeClass()
{
    Clear();
}

void SomeClass::Clear()
{
    if (MyText)
        free( MyText);

    MyText = NULL;
}



void SomeClass::Settext( const char *text)
{
    Clear();

    MyText = malloc( strlen(text));

    if (MyText)
        strcpy( MyText, text);
}

Güncellenen soru elbette cevabımı yanlış yapıyor (veya en azından başka bir sorunun cevabını). Temel olarak, API'yi kötüye kullanırlarsa çökmelerine izin verin diyen cevaplara katılıyorum. İnsanların başparmağına çekiçle vurmasını durduramazsınız ...
Tim Ring

2

Herkese açık bir API'de giriş parametreleri olarak rastgele işaretçileri kabul etmek çok iyi bir politika değildir. Bir tamsayı, dizge veya yapı gibi "düz veri" türlerine sahip olmak daha iyidir (tabii ki içinde düz veri olan klasik bir yapıyı kastediyorum; resmi olarak her şey bir yapı olabilir).

Neden? Çünkü diğerlerinin dediği gibi, size geçerli bir işaretçi mi yoksa önemsiz işaret eden bir işaretçi mi verildiğini bilmenin standart bir yolu yoktur.

Ancak bazen seçeneğiniz olmaz - API'nizin bir işaretçiyi kabul etmesi gerekir.

Bu durumlarda, iyi bir işaretçiyi geçmek arayanın görevidir. NULL bir değer olarak kabul edilebilir, ancak önemsiz bir gösterici olamaz.

Herhangi bir şekilde tekrar kontrol edebilir misin? Böyle bir durumda yaptığım şey, işaretçinin işaret ettiği tür için bir değişmez tanımlamak ve onu aldığınızda çağırmaktı (hata ayıklama modunda). En azından değişmez başarısız olursa (veya çökerse), size kötü bir değer geçtiğini bilirsiniz.

// API that does not allow NULL
void PublicApiFunction1(Person* in_person)
{
  assert(in_person != NULL);
  assert(in_person->Invariant());

  // Actual code...
}

// API that allows NULL
void PublicApiFunction2(Person* in_person)
{
  assert(in_person == NULL || in_person->Invariant());

  // Actual code (must keep in mind that in_person may be NULL)
}

re: "düz bir veri türü iletin ... bir dize gibi" Ancak C ++ 'da dizeler genellikle işaretçiler olarak (char *) veya (const char *) karakterlere aktarılır, bu nedenle işaretçileri geçirmeye geri dönersiniz. Ve örneğiniz in_person'ı bir işaretçi olarak değil, bir başvuru olarak iletir, bu nedenle karşılaştırma (in_person! = NULL), Person sınıfında tanımlanmış bazı nesne / işaretçi karşılaştırmaları olduğunu gösterir.
Jesse Chisholm

@JesseChisholm string ile bir dizeyi kastetmiştim, yani bir std :: string. Hiçbir şekilde dizeleri depolamak veya dağıtmak için char * kullanmanızı önermiyorum. Bunu yapma.
Daniel Daranas

@JesseChisholm Nedense beş yıl önce bu soruyu cevaplarken bir hata yaptım. Açıkçası, bir Kişinin & BOŞ olup olmadığını kontrol etmenin bir anlamı yoktur. Bu derlenmez bile. Referansları değil, işaretçileri kullanmak istiyordum. Şimdi düzelttim.
Daniel Daranas

1

Başkalarının dediği gibi, geçersiz bir işaretçiyi güvenilir bir şekilde tespit edemezsiniz. Geçersiz bir işaretçinin alabileceği bazı formları düşünün:

Boş göstericiniz olabilir. Bu, kolayca kontrol edebileceğiniz ve hakkında bir şeyler yapabileceğiniz bir şey.

Geçerli hafızanın dışında bir yere bir işaretçiniz olabilir. Geçerli belleği neyin oluşturduğu, sisteminizin çalışma zamanı ortamının adres alanını nasıl ayarladığına bağlı olarak değişir. Unix sistemlerinde, genellikle 0'dan başlayan ve çok sayıda megabayta kadar giden sanal bir adres alanıdır. Gömülü sistemlerde oldukça küçük olabilir. Her halükarda 0'dan başlamayabilir. Uygulamanız gözetmen modunda veya eşdeğerinde çalışıyorsa, işaretçiniz gerçek bir adrese başvurabilir ve bu gerçek bellekle yedeklenebilir veya yedeklenmeyebilir.

Geçerli belleğinizin içinde, veri segmentinizin, bss'inizin, yığınınızın veya yığınınızın içinde bile, ancak geçerli bir nesneyi işaret etmeyen bir işaretçiniz olabilir. Bunun bir çeşidi, nesneye kötü bir şey olmadan önce geçerli bir nesneyi işaret etmek için kullanılan bir işaretçidir. Bu bağlamdaki kötü şeyler arasında serbest bırakma, bellek bozulması veya işaretçi bozulması bulunur.

Referans verilen şey için kuraldışı hizalamaya sahip bir işaretçi gibi düz bir kuraldışı işaretçiniz olabilir.

Segment / ofset tabanlı mimarileri ve diğer garip işaretçi uygulamalarını düşündüğünüzde sorun daha da kötüleşiyor. Bu tür şeyler normalde iyi derleyiciler ve türlerin mantıklı kullanımı tarafından geliştiriciden gizlenir, ancak perdeyi delmek ve işletim sistemini ve derleyici geliştiricilerini alt etmek istiyorsanız, yapabilirsiniz, ancak genel bir yol yoktur. karşılaşabileceğiniz tüm sorunların üstesinden gelmek için.

Yapabileceğiniz en iyi şey, kazaya izin vermek ve bazı iyi teşhis bilgileri ortaya koymaktır.


re: "bazı iyi teşhis bilgileri koyun", sorun var. İşaretçi geçerliliğini kontrol edemediğiniz için, uğraşmanız gereken bilgiler minimum düzeydedir. "Burada bir istisna oldu" belki de tüm elde ettiğiniz olabilir. Tüm çağrı yığını güzeldir, ancak çoğu C ++ çalışma zamanı kitaplığının sağladığından daha iyi bir çerçeve gerektirir.
Jesse Chisholm


1

Genel olarak yapmak imkansızdır. İşte özellikle kötü bir durum:

struct Point2d {
    int x;
    int y;
};

struct Point3d {
    int x;
    int y;
    int z;
};

void dump(Point3 *p)
{
    printf("[%d %d %d]\n", p->x, p->y, p->z);
}

Point2d points[2] = { {0, 1}, {2, 3} };
Point3d *p3 = reinterpret_cast<Point3d *>(&points[0]);
dump(p3);

Birçok platformda şu çıktı alınır:

[0 1 2]

Çalışma zamanı sistemini bellek bitlerini yanlış yorumlamaya zorluyorsunuz, ancak bu durumda çökmeyecek çünkü bitlerin hepsi mantıklı. Bu (C tarzı polimorfizmi bakmak dilin tasarımının bir parçası olan struct inaddr, inaddr_in, inaddr_in6güvenilir bir her platformda buna karşı koruyamaz böylece).


1

Yukarıdaki makalelerde ne kadar yanıltıcı bilgi okuyabileceğiniz inanılmaz ...

Ve hatta microsoft msdn belgelerinde IsBadPtr'in yasaklandığı iddia ediliyor. Oh peki - çökmek yerine çalışan uygulamayı tercih ederim. Terim çalışması yanlış çalışıyor olsa bile (son kullanıcı uygulamaya devam edebildiği sürece).

Google'da pencereler için yararlı bir örnek bulamadım - 32 bit uygulamalar için bir çözüm buldum,

http://www.codeproject.com/script/Content/ViewAssociatedFile.aspx?rzp=%2FKB%2Fsystem%2Fdetect-driver%2F%2FDetectDriverSrc.zip&zep=DetectDriverSrc%2FDetectDriver%2Fsrc%2FdrvCppid95 = 2

ancak 64 bit uygulamaları da desteklemem gerekiyor, bu yüzden bu çözüm benim için işe yaramadı.

Ancak, wine kaynak kodlarını topladım ve 64 bit uygulamalar için de işe yarayacak benzer türde bir kod hazırlamayı başardım - kodu buraya ekleyerek:

#include <typeinfo.h>   

typedef void (*v_table_ptr)();   

typedef struct _cpp_object   
{   
    v_table_ptr*    vtable;   
} cpp_object;   



#ifndef _WIN64
typedef struct _rtti_object_locator
{
    unsigned int signature;
    int base_class_offset;
    unsigned int flags;
    const type_info *type_descriptor;
    //const rtti_object_hierarchy *type_hierarchy;
} rtti_object_locator;
#else

typedef struct
{
    unsigned int signature;
    int base_class_offset;
    unsigned int flags;
    unsigned int type_descriptor;
    unsigned int type_hierarchy;
    unsigned int object_locator;
} rtti_object_locator;  

#endif

/* Get type info from an object (internal) */  
static const rtti_object_locator* RTTI_GetObjectLocator(void* inptr)  
{   
    cpp_object* cppobj = (cpp_object*) inptr;  
    const rtti_object_locator* obj_locator = 0;   

    if (!IsBadReadPtr(cppobj, sizeof(void*)) &&   
        !IsBadReadPtr(cppobj->vtable - 1, sizeof(void*)) &&   
        !IsBadReadPtr((void*)cppobj->vtable[-1], sizeof(rtti_object_locator)))  
    {  
        obj_locator = (rtti_object_locator*) cppobj->vtable[-1];  
    }  

    return obj_locator;  
}  

Ve aşağıdaki kod, işaretçinin geçerli olup olmadığını algılayabilir, muhtemelen bazı NULL denetimi eklemeniz gerekir:

    CTest* t = new CTest();
    //t = (CTest*) 0;
    //t = (CTest*) 0x12345678;

    const rtti_object_locator* ptr = RTTI_GetObjectLocator(t);  

#ifdef _WIN64
    char *base = ptr->signature == 0 ? (char*)RtlPcToFileHeader((void*)ptr, (void**)&base) : (char*)ptr - ptr->object_locator;
    const type_info *td = (const type_info*)(base + ptr->type_descriptor);
#else
    const type_info *td = ptr->type_descriptor;
#endif
    const char* n =td->name();

Bu, sınıf adını göstericiden alır - ihtiyaçlarınız için yeterli olması gerektiğini düşünüyorum.

Hala korktuğum bir şey, işaretçi kontrolünün performansı - yukarıdaki kod parçacığında zaten 3-4 API çağrısı yapılıyor - zaman açısından kritik uygulamalar için aşırı olabilir.

Örneğin, C # / yönetilen c ++ çağrılarına kıyasla işaretçi kontrolünün ek yükünü ölçebilmesi iyi olur.


1

Aslında, belirli durumlarda bir şeyler yapılabilir: örneğin, bir dizge işaretçi dizesinin geçerli olup olmadığını kontrol etmek istiyorsanız, write (fd, buf, szie) syscall kullanmak sihri yapmanıza yardımcı olabilir: let fd, geçici bir dosya tanımlayıcısı olsun. test için oluşturduğunuz dosya ve test ettiğiniz dizeyi işaret eden buf, eğer işaretçi geçersizse write () -1 döndürür ve errno, buf'un erişilebilir adres alanınızın dışında olduğunu belirten EFAULT olarak ayarlanır.


1

Aşağıdakiler Windows'ta çalışıyor (daha önce birisi önerdi):

 static void copy(void * target, const void* source, int size)
 {
     __try
     {
         CopyMemory(target, source, size);
     }
     __except(EXCEPTION_EXECUTE_HANDLER)
     {
         doSomething(--whatever--);
     }
 }

İşlev, bazı sınıfların statik, bağımsız veya statik yöntemi olmalıdır. Salt okunurda test etmek için, verileri yerel arabelleğe kopyalayın. İçeriği değiştirmeden yazmayı test etmek için üzerine yazın. Yalnızca ilk / son adresleri test edebilirsiniz. İşaretçi geçersizse, denetim 'doSomething'e ve ardından parantezlerin dışına aktarılacaktır. CString gibi yıkıcı gerektiren hiçbir şey kullanmayın.


1

Windows'ta bu kodu kullanıyorum:

void * G_pPointer = NULL;
const char * G_szPointerName = NULL;
void CheckPointerIternal()
{
    char cTest = *((char *)G_pPointer);
}
bool CheckPointerIternalExt()
{
    bool bRet = false;

    __try
    {
        CheckPointerIternal();
        bRet = true;
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
    }

    return  bRet;
}
void CheckPointer(void * A_pPointer, const char * A_szPointerName)
{
    G_pPointer = A_pPointer;
    G_szPointerName = A_szPointerName;
    if (!CheckPointerIternalExt())
        throw std::runtime_error("Invalid pointer " + std::string(G_szPointerName) + "!");
}

Kullanım:

unsigned long * pTest = (unsigned long *) 0x12345;
CheckPointer(pTest, "pTest"); //throws exception

0

Windows için IsBadReadPtr (), IsBadWritePtr (), IsBadCodePtr (), IsBadStringPtr ().
Bunlar bloğun uzunluğu ile orantılı zaman alır, bu yüzden akıl sağlığı kontrolü için sadece başlangıç ​​adresini kontrol ediyorum.


3
işe yaramadıkları için bu yöntemlerden kaçınmalısınız. blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx
JaredPar

Bazen çalışmamaları için geçici çözümler olabilir: stackoverflow.com/questions/496034/…
ChrisW

0

Çeşitli kütüphanelerin başvurulmamış bellek ve benzeri şeyleri kontrol etmek için bazı yöntemler kullandığını gördüm. İşaretçilerin kaydını tutan bir mantığa sahip olan bellek ayırma ve serbest bırakma yöntemlerini (malloc / free) basitçe "geçersiz kıldıklarına" inanıyorum. Sanırım bu, kullanım durumunuz için aşırı bir şey, ama bunu yapmanın bir yolu olabilir.


Bu, maalesef yığın ayrılmış nesneler için yardımcı olmuyor.
Tom

0

Teknik olarak, yeni operatörü geçersiz kılabilir (ve silebilir ) ve ayrılmış tüm bellek hakkında bilgi toplayabilirsiniz, böylece yığın belleğinin geçerli olup olmadığını kontrol etmek için bir yönteme sahip olabilirsiniz. fakat:

  1. yine de işaretçinin stack () üzerinde tahsis edilip edilmediğini kontrol etmenin bir yolunu

  2. 'geçerli' göstericinin ne olduğunu tanımlamanız gerekecek:

a) bu adres için bellek tahsis edilir

b) bu ​​adresteki hafıza başlar nesnenin adresidir (örneğin adres, büyük dizinin ortasında değildir)

c) bu adresteki bellek, beklenen tipteki nesnenin başlangıç adresidir

Sonuç olarak : söz konusu yaklaşım C ++ yolu değildir, fonksiyonun geçerli işaretçiler almasını sağlayacak bazı kurallar tanımlamanız gerekir.



0

Kabul edilen cevaplara ek:

İmlecinizin yalnızca üç değeri tutabileceğini varsayın - 0, 1 ve -1 burada 1 geçerli bir göstericiyi, -1 geçersiz bir değeri ve 0 başka bir geçersiz değeri gösterir. İşaretçinizin olasılığı nedir olduğu NULL, tüm değerler eşit olasılıkla olmak? 1/3. Şimdi, geçerli vakayı çıkarın, böylece her geçersiz durum için, tüm hataları yakalamak için 50:50 oranınız olur. İyi görünüyor değil mi? Bunu 4 baytlık bir işaretçi için ölçeklendirin. 2 ^ 32 veya 4294967294 olası değer vardır. Bunlardan yalnızca BİR değer doğrudur, biri NULL'dur ve hala 4294967292 diğer geçersiz vakalarla kalırsınız. Yeniden hesaplayın: (4294967292+ 1) geçersiz vakadan 1'i için bir testiniz var. Çoğu pratik amaç için 2.xe-10 veya 0 olasılığı. NULL kontrolünün beyhudeliği budur.


0

Biliyorsunuz, bunu yapabilen yeni bir sürücü (en azından Linux'ta) muhtemelen yazmak o kadar da zor olmayacaktır.

Öte yandan, programlarınızı böyle inşa etmek aptallık olur. Böyle bir şey için gerçekten özel ve tek kullanımlık bir kullanımınız yoksa, bunu tavsiye etmem. Sabit işaretçi geçerlilik denetimleri ile yüklenmiş büyük bir uygulama oluşturduysanız, muhtemelen korkunç derecede yavaş olacaktır.


0

işe yaramadıkları için bu yöntemlerden kaçınmalısınız. blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx - JaredPar 15 Şub09 16:02

Çalışmazlarsa - sonraki Windows güncellemesi sorunu çözecek mi? Kavram düzeyinde çalışmazlarsa, işlev muhtemelen Windows api'den tamamen kaldırılacaktır.

MSDN belgeleri, yasaklandıklarını iddia ediyor ve bunun nedeni muhtemelen daha fazla uygulama tasarımının kusurudur (örneğin, genel olarak geçersiz işaretçileri sessizce yememelisiniz - eğer tüm uygulamanın tasarımından sorumluysanız) ve performans / zaman işaretçi kontrolü.

Ancak bazı bloglar yüzünden çalışmadıklarını iddia etmemelisiniz. Test uygulamamda işe yaradıklarını doğruladım.


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.