Gibt es eine Möglichkeit (natürlich programmatisch) festzustellen, ob ein bestimmter Zeiger "gültig" ist? Das Überprüfen auf NULL ist einfach, aber was ist mit Dingen wie 0x00001234? Beim Versuch, diese Art von Zeiger zu dereferenzieren, tritt eine Ausnahme / ein Absturz auf.
Eine plattformübergreifende Methode wird bevorzugt, aber auch plattformspezifisch (für Windows und Linux) ist in Ordnung.
Update zur Verdeutlichung: Das Problem liegt nicht bei veralteten / freigegebenen / nicht initialisierten Zeigern. Stattdessen implementiere ich eine API, die Zeiger vom Aufrufer übernimmt (wie einen Zeiger auf eine Zeichenfolge, ein Dateihandle usw.). Der Anrufer kann (absichtlich oder versehentlich) einen ungültigen Wert als Zeiger senden. Wie verhindere ich einen Absturz?
Antworten:
Sie können diese Prüfung nicht durchführen. Sie können einfach nicht überprüfen, ob ein Zeiger "gültig" ist. Sie müssen darauf vertrauen, dass Menschen, die eine Funktion verwenden, die einen Zeiger verwendet, wissen, was sie tun. Wenn sie Ihnen 0x4211 als Zeigerwert übergeben, müssen Sie darauf vertrauen, dass er auf die Adresse 0x4211 zeigt. Und wenn sie "versehentlich" auf ein Objekt treffen, würden Sie selbst dann, wenn Sie eine beängstigende Betriebssystemfunktion (IsValidPtr oder was auch immer) verwenden würden, in einen Fehler geraten und nicht schnell ausfallen.
Verwenden Sie Nullzeiger, um diese Art von Dingen zu signalisieren, und teilen Sie dem Benutzer Ihrer Bibliothek mit, dass er keine Zeiger verwenden soll, wenn er dazu neigt, versehentlich ungültige Zeiger zu übergeben, ernsthaft :)
quelle
Hier sind drei einfache Möglichkeiten für ein C-Programm unter Linux, sich einen Überblick über den Status des Speichers zu verschaffen, in dem es ausgeführt wird, und warum die Frage in einigen Kontexten angemessene Antworten bietet.
Unter Microsoft Windows gibt es die Funktion QueryWorkingSetEx, die unter der Prozessstatus-API (auch in der NUMA-API) dokumentiert ist. Als Folge einer ausgeklügelten NUMA-API-Programmierung können Sie mit dieser Funktion auch einfache "Testzeiger auf Gültigkeit (C / C ++)" ausführen, da es unwahrscheinlich ist, dass sie für mindestens 15 Jahre veraltet sind.
quelle
Das Verhindern eines Absturzes, der dadurch verursacht wird, dass der Anrufer einen ungültigen Zeiger sendet, ist eine gute Möglichkeit, stille Fehler zu machen, die schwer zu finden sind.
Ist es nicht besser für den Programmierer, der Ihre API verwendet, eine klare Nachricht zu erhalten, dass sein Code falsch ist, indem er ihn zum Absturz bringt, anstatt ihn auszublenden?
quelle
Unter Win32 / 64 gibt es eine Möglichkeit, dies zu tun. Versuchen Sie, den Zeiger zu lesen und die resultierende SEH-Ausnahme abzufangen, die bei einem Fehler ausgelöst wird. Wenn es nicht wirft, ist es ein gültiger Zeiger.
Das Problem bei dieser Methode ist jedoch, dass nur zurückgegeben wird, ob Sie Daten vom Zeiger lesen können oder nicht. Es gibt keine Garantie für die Typensicherheit oder eine beliebige Anzahl anderer Invarianten. Im Allgemeinen ist diese Methode für nichts anderes gut, als zu sagen: "Ja, ich kann diesen bestimmten Ort im Speicher zu einem Zeitpunkt lesen, der jetzt vergangen ist."
Kurz gesagt, tu das nicht;)
Raymond Chen hat einen Blog-Beitrag zu diesem Thema: http://blogs.msdn.com/oldnewthing/archive/2007/06/25/3507294.aspx
quelle
AFAIK gibt es keinen Weg. Sie sollten versuchen, diese Situation zu vermeiden, indem Sie nach dem Freigeben des Speichers immer die Zeiger auf NULL setzen.
quelle
Schauen Sie sich diese und diese Frage an. Schauen Sie sich auch intelligente Zeiger an .
quelle
In Bezug auf die Antwort etwas weiter oben in diesem Thread:
Mein Rat ist, sich von ihnen fernzuhalten, jemand hat diesen bereits gepostet: http://blogs.msdn.com/oldnewthing/archive/2007/06/25/3507294.aspx
Ein weiterer Beitrag zum selben Thema und vom selben Autor (glaube ich) ist dieser: http://blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx ("IsBadXxxPtr sollte eigentlich CrashProgramRandomly heißen ").
Wenn die Benutzer Ihrer API fehlerhafte Daten senden, lassen Sie diese abstürzen. Wenn das Problem darin besteht, dass die übergebenen Daten erst später verwendet werden (und dies das Auffinden der Ursache erschwert), fügen Sie einen Debug-Modus hinzu, in dem die Zeichenfolgen usw. bei der Eingabe protokolliert werden. Wenn sie schlecht sind, wird es offensichtlich sein (und wahrscheinlich abstürzen). Wenn es viel zu oft vorkommt, lohnt es sich möglicherweise, Ihre API aus dem Prozess zu entfernen und den API-Prozess anstelle des Hauptprozesses zum Absturz zu bringen.
quelle
Erstens sehe ich keinen Grund darin, mich vor dem Anrufer zu schützen, der absichtlich versucht, einen Absturz zu verursachen. Sie könnten dies leicht tun, indem sie versuchen, über einen ungültigen Zeiger selbst darauf zuzugreifen. Es gibt viele andere Möglichkeiten - sie könnten einfach Ihren Speicher oder den Stapel überschreiben. Wenn Sie sich vor solchen Dingen schützen müssen, müssen Sie in einem separaten Prozess mit Sockets oder einem anderen IPC für die Kommunikation ausgeführt werden.
Wir schreiben eine Menge Software, mit der Partner / Kunden / Benutzer die Funktionalität erweitern können. Unweigerlich wird jeder Fehler zuerst an uns gemeldet, daher ist es hilfreich, leicht nachweisen zu können, dass das Problem im Plug-In-Code liegt. Darüber hinaus gibt es Sicherheitsbedenken und einige Benutzer sind vertrauenswürdiger als andere.
Wir verwenden verschiedene Methoden, abhängig von den Leistungs- / Durchsatzanforderungen und der Vertrauenswürdigkeit. Von den am meisten bevorzugten:
separate Prozesse mithilfe von Sockets (häufig Daten als Text übergeben).
separate Prozesse unter Verwendung des gemeinsam genutzten Speichers (wenn große Datenmengen übertragen werden sollen).
Gleicher Prozess trennt Threads über die Nachrichtenwarteschlange (wenn häufige Kurznachrichten vorhanden sind).
Im selben Prozess werden alle übergebenen Daten aus einem Speicherpool getrennt.
Gleicher Prozess über direkten Prozeduraufruf - alle übergebenen Daten aus einem Speicherpool.
Wir versuchen, niemals auf das zurückzugreifen, was Sie beim Umgang mit Software von Drittanbietern versuchen - insbesondere, wenn wir die Plug-Ins / Bibliothek als Binär- und nicht als Quellcode erhalten.
Die Verwendung eines Speicherpools ist in den meisten Fällen recht einfach und muss nicht ineffizient sein. Wenn SIE die Daten zuerst zuweisen, ist es trivial, die Zeiger mit den von Ihnen zugewiesenen Werten zu vergleichen. Sie können auch die zugewiesene Länge speichern und vor und nach den Daten "magische" Werte hinzufügen, um nach gültigen Datentypen und Datenüberschreitungen zu suchen.
quelle
Ich habe viel Verständnis für Ihre Frage, da ich selbst in einer fast identischen Position bin. Ich schätze , was viele der Antworten sagen, und sie sind richtig - die Routine der Zeiger liefern soll einen gültigen Zeiger weitergeben. In meinem Fall ist es fast unvorstellbar, dass sie den Zeiger beschädigt haben könnten - aber wenn sie es getan hätten geschafft hätten, wäre es MEINE Software, die abstürzt, und ME, die die Schuld bekommen würde :-(
Meine Anforderung ist nicht, dass ich nach einem Segmentierungsfehler fortfahre - das wäre gefährlich - ich möchte nur dem Kunden melden, was vor der Kündigung passiert ist, damit er seinen Code reparieren kann, anstatt mich zu beschuldigen!
So habe ich es gefunden (unter Windows): http://www.cplusplus.com/reference/clibrary/csignal/signal/
Um eine Zusammenfassung zu geben:
Nun scheint sich dies so zu verhalten, wie ich es mir erhoffen würde (es gibt die Fehlermeldung aus und beendet dann das Programm) - aber wenn jemand einen Fehler entdecken kann, lassen Sie es mich bitte wissen!
quelle
exit()
, es umgeht RAII und kann daher zu Ressourcenlecks führen.exit()
und meine tragbare C ++ - Alarmglocke begann zu läuten. Es sollte in dieser Linux-spezifischen Situation in Ordnung sein, in der Ihr Programm sowieso beendet wird, entschuldigen Sie das Rauschen.man 2 signal
Unter Linux gibt es einen Absatz, in dem erklärt wird, warum.Unter Unix sollten Sie in der Lage sein, einen Kernel-Systemaufruf zu verwenden, der Zeigerprüfungen durchführt und EFAULT zurückgibt, z.
Rückkehr:
Es gibt wahrscheinlich einen besseren Systemaufruf als open () [möglicherweise Zugriff], da die Möglichkeit besteht, dass dies zu einem tatsächlichen Codepfad für die Dateierstellung und einer anschließenden Anforderung zum Schließen führt.
quelle
In C ++ gibt es keine Bestimmungen zum Testen der Gültigkeit eines Zeigers als allgemeiner Fall. Man kann natürlich davon ausgehen, dass NULL (0x00000000) schlecht ist, und verschiedene Compiler und Bibliotheken verwenden hier und da gerne "spezielle Werte", um das Debuggen zu vereinfachen (zum Beispiel, wenn ich jemals einen Zeiger als 0xCECECECE in Visual Studio sehe, den ich kenne Ich habe etwas falsch gemacht), aber die Wahrheit ist, dass es nahezu unmöglich ist, anhand eines Zeigers zu erkennen, ob es sich um den "richtigen" Index handelt, da ein Zeiger nur ein Index im Speicher ist.
Es gibt verschiedene Tricks, die Sie mit dynamic_cast und RTTI ausführen können, um sicherzustellen, dass das Objekt, auf das verwiesen wird, vom gewünschten Typ ist. Sie alle erfordern jedoch, dass Sie auf etwas Gültiges verweisen.
Wenn Sie sicherstellen möchten, dass Ihr Programm "ungültige" Zeiger erkennen kann, lautet mein Rat: Setzen Sie jeden Zeiger, den Sie deklarieren, sofort nach der Erstellung auf NULL oder eine gültige Adresse und setzen Sie ihn unmittelbar nach dem Freigeben des Speichers, auf den er verweist, auf NULL. Wenn Sie diese Praxis sorgfältig anwenden, ist die Überprüfung auf NULL alles, was Sie jemals brauchen.
quelle
Es gibt keine tragbare Möglichkeit, dies zu tun, und es kann für bestimmte Plattformen zwischen schwierig und unmöglich sein. In jedem Fall sollten Sie niemals Code schreiben, der von einer solchen Prüfung abhängt - lassen Sie die Zeiger überhaupt nicht ungültige Werte annehmen.
quelle
Das Setzen des Zeigers auf NULL vor und nach der Verwendung ist eine gute Technik. Dies ist in C ++ einfach, wenn Sie beispielsweise Zeiger innerhalb einer Klasse (einer Zeichenfolge) verwalten:
quelle
Es ist keine sehr gute Richtlinie, beliebige Zeiger als Eingabeparameter in einer öffentlichen API zu akzeptieren. Es ist besser, "einfache Datentypen" wie eine Ganzzahl, eine Zeichenfolge oder eine Struktur zu haben (ich meine natürlich eine klassische Struktur mit einfachen Daten darin; offiziell kann alles eine Struktur sein).
Warum? Nun, wie andere sagen, gibt es keine Standardmethode, um festzustellen, ob Sie einen gültigen Zeiger erhalten haben oder einen, der auf Junk verweist.
Aber manchmal haben Sie keine Wahl - Ihre API muss einen Zeiger akzeptieren.
In diesen Fällen ist es die Pflicht des Anrufers, einen guten Zeiger zu übergeben. NULL kann als Wert akzeptiert werden, aber kein Zeiger auf Junk.
Können Sie das in irgendeiner Weise überprüfen? In einem solchen Fall habe ich eine Invariante für den Typ definiert, auf den der Zeiger zeigt, und sie aufgerufen, wenn Sie sie erhalten (im Debug-Modus). Zumindest wenn die Invariante ausfällt (oder abstürzt), wissen Sie, dass Ihnen ein schlechter Wert übergeben wurde.
quelle
Wie andere gesagt haben, können Sie einen ungültigen Zeiger nicht zuverlässig erkennen. Betrachten Sie einige der Formen, die ein ungültiger Zeiger annehmen könnte:
Sie könnten einen Nullzeiger haben. Das ist eine, nach der Sie leicht suchen und etwas unternehmen können.
Sie könnten einen Zeiger auf etwas außerhalb des gültigen Speichers haben. Was einen gültigen Speicher ausmacht, hängt davon ab, wie die Laufzeitumgebung Ihres Systems den Adressraum einrichtet. Auf Unix-Systemen ist es normalerweise ein virtueller Adressraum, der bei 0 beginnt und eine große Anzahl von Megabyte erreicht. Auf eingebetteten Systemen kann es recht klein sein. Es könnte auf keinen Fall bei 0 beginnen. Wenn Ihre App zufällig im Supervisor-Modus oder einem gleichwertigen Modus ausgeführt wird, verweist Ihr Zeiger möglicherweise auf eine reale Adresse, die möglicherweise mit echtem Speicher gesichert ist oder nicht.
Sie könnten einen Zeiger auf irgendwo in Ihrem gültigen Speicher haben, sogar in Ihrem Datensegment, BSS, Stack oder Heap, aber nicht auf ein gültiges Objekt zeigen. Eine Variante davon ist ein Zeiger, der auf ein gültiges Objekt zeigte, bevor dem Objekt etwas Schlimmes passiert ist. Zu den schlechten Dingen in diesem Zusammenhang gehören Freigabe, Speicherbeschädigung oder Zeigerbeschädigung.
Möglicherweise haben Sie einen unzulässigen unzulässigen Zeiger, z. B. einen Zeiger mit unzulässiger Ausrichtung für das Objekt, auf das verwiesen wird.
Das Problem wird noch schlimmer, wenn Sie segment- / versatzbasierte Architekturen und andere ungerade Zeigerimplementierungen berücksichtigen. Diese Art von Dingen wird dem Entwickler normalerweise durch gute Compiler und den umsichtigen Einsatz von Typen verborgen. Wenn Sie jedoch den Schleier durchstoßen und versuchen möchten, das Betriebssystem und die Compiler-Entwickler zu überlisten, können Sie dies, aber es gibt keinen generischen Weg Um dies zu tun, werden alle Probleme behandelt, auf die Sie möglicherweise stoßen.
Das Beste, was Sie tun können, ist, den Absturz zuzulassen und einige gute Diagnoseinformationen herauszugeben.
quelle
Dieser Artikel MEM10-C. Das Definieren und Verwenden einer Zeigerüberprüfungsfunktion besagt, dass eine Überprüfung bis zu einem gewissen Grad möglich ist, insbesondere unter Linux.
quelle
Im Allgemeinen ist das unmöglich. Hier ist ein besonders schlimmer Fall:
Auf vielen Plattformen wird dies ausgedruckt:
Sie zwingen das Laufzeitsystem, Speicherbits falsch zu interpretieren, aber in diesem Fall stürzt es nicht ab, da alle Bits sinnvoll sind. Dies ist Teil des Entwurfs der Sprache (siehe C-Stil - Polymorphismus mit
struct inaddr
,inaddr_in
,inaddr_in6
), so dass Sie nicht zuverlässig gegen sie auf jeder Plattform schützen.quelle
Es ist unglaublich, wie viele irreführende Informationen Sie in den obigen Artikeln lesen können ...
Und selbst in der Microsoft MSDN-Dokumentation wird behauptet, IsBadPtr sei verboten. Na ja - ich arbeite lieber mit einer Anwendung als mit einem Absturz. Auch wenn das Arbeiten mit Begriffen möglicherweise nicht ordnungsgemäß funktioniert (solange der Endbenutzer mit der Anwendung fortfahren kann).
Durch Googeln habe ich kein nützliches Beispiel für Windows gefunden - eine Lösung für 32-Bit-Apps gefunden,
http://www.codeproject.com/script/Content/ViewAssociatedFile.aspx?rzp=%2FKB%2Fsystem%2Fdetect-driver%2F%2FDetectDriverSrc.zip&zep=DetectDriverSrc%2FDetectDriver%2Fsrctt2 = 2
Ich muss aber auch 64-Bit-Apps unterstützen, sodass diese Lösung bei mir nicht funktioniert hat.
Aber ich habe die Quellcodes von Wein geerntet und es geschafft, einen ähnlichen Code zu erstellen, der auch für 64-Bit-Apps funktioniert - Code hier anhängen:
Wenn der folgende Code erkennt, ob der Zeiger gültig ist oder nicht, müssen Sie wahrscheinlich eine NULL-Prüfung hinzufügen:
Dies erhält den Klassennamen vom Zeiger - ich denke, es sollte für Ihre Bedürfnisse ausreichen.
Eine Sache, die ich immer noch befürchte, ist die Leistung der Zeigerprüfung - im obigen Code-Snipet werden bereits 3-4 API-Aufrufe ausgeführt -, die für zeitkritische Anwendungen möglicherweise zu viel des Guten sind.
Es wäre gut, wenn jemand den Overhead der Zeigerprüfung im Vergleich zu beispielsweise C # / verwalteten C ++ - Aufrufen messen könnte.
quelle
In der Tat könnte unter bestimmten Umständen etwas getan werden: Wenn Sie beispielsweise überprüfen möchten, ob eine Zeichenfolgezeigerzeichenfolge gültig ist, können Sie mit write (fd, buf, szie) syscall die Magie ausführen: Lassen Sie fd ein Dateideskriptor für temporär sein Datei, die Sie für den Test erstellen, und Buf, der auf die Zeichenfolge zeigt, die Sie testen. Wenn der Zeiger ungültig ist, gibt write () -1 zurück und errno wird auf EFAULT gesetzt, was darauf hinweist, dass sich Buf außerhalb Ihres zugänglichen Adressraums befindet.
quelle
Folgendes funktioniert unter Windows (jemand hat es zuvor vorgeschlagen):
Die Funktion muss eine statische, eigenständige oder statische Methode einer Klasse sein. Kopieren Sie die Daten in den lokalen Puffer, um sie schreibgeschützt zu testen. Um das Schreiben zu testen, ohne den Inhalt zu ändern, schreiben Sie es über. Sie können nur die erste / letzte Adresse testen. Wenn der Zeiger ungültig ist, wird die Steuerung an 'doSomething' und dann außerhalb der Klammern übergeben. Verwenden Sie nur keine Destruktoren wie CString.
quelle
Unter Windows verwende ich diesen Code:
Verwendung:
quelle
IsBadReadPtr (), IsBadWritePtr (), IsBadCodePtr (), IsBadStringPtr () für Windows.
Diese benötigen Zeit proportional zur Länge des Blocks, daher überprüfe ich zur Überprüfung der Gesundheit nur die Startadresse.
quelle
Ich habe gesehen, dass verschiedene Bibliotheken eine Methode verwenden, um nach nicht referenziertem Speicher und dergleichen zu suchen. Ich glaube, sie "überschreiben" einfach die Speicherzuweisungs- und Freigabemethoden (malloc / free), die eine Logik haben, die die Zeiger verfolgt. Ich nehme an, dies ist ein Overkill für Ihren Anwendungsfall, aber es wäre eine Möglichkeit, dies zu tun.
quelle
Technisch gesehen können Sie den Operator new überschreiben (und löschen ) und Informationen über den gesamten zugewiesenen Speicher sammeln, sodass Sie mithilfe einer Methode überprüfen können, ob der Heapspeicher gültig ist. aber:
Sie müssen noch prüfen, ob der Zeiger auf stack () zugeordnet ist.
Sie müssen definieren, was ein 'gültiger' Zeiger ist:
a) Speicher an dieser Adresse wird zugewiesen
b) Der Speicher an dieser Adresse wird gestartet Startadresse des Objekts (z. B. Adresse, die sich nicht in der Mitte eines riesigen Arrays befindet)
c) Der Speicher an dieser Adresse ist die Startadresse des Objekts des erwarteten Typs
Fazit : Der fragliche Ansatz ist kein C ++ - Ansatz. Sie müssen einige Regeln definieren, die sicherstellen, dass die Funktion gültige Zeiger empfängt.
quelle
Es gibt keine Möglichkeit, diese Prüfung in C ++ durchzuführen. Was sollten Sie tun, wenn Ihnen anderer Code einen ungültigen Zeiger übergibt? Du solltest abstürzen. Warum? Überprüfen Sie diesen Link: http://blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx
quelle
Nachtrag zu den erreichten Antworten:
Angenommen, Ihr Zeiger könnte nur drei Werte enthalten - 0, 1 und -1, wobei 1 einen gültigen Zeiger, -1 einen ungültigen und 0 einen anderen ungültigen Zeiger bedeutet. Was ist die Wahrscheinlichkeit, dass Ihr Zeiger ist NULL, wobei alle Werte gleich wahrscheinlich? 1/3. Nehmen Sie nun den gültigen Fall heraus, sodass Sie für jeden ungültigen Fall ein Verhältnis von 50:50 haben, um alle Fehler abzufangen. Sieht gut aus, oder? Skalieren Sie dies für einen 4-Byte-Zeiger. Es gibt 2 ^ 32 oder 4294967294 mögliche Werte. Von diesen ist nur EIN Wert korrekt, einer ist NULL und Sie haben noch 4294967292 andere ungültige Fälle. Neu berechnen: Sie haben einen Test für 1 von (4294967292+ 1) ungültigen Fällen. Eine Wahrscheinlichkeit von 2.xe-10 oder 0 für die meisten praktischen Zwecke. Dies ist die Sinnlosigkeit des NULL-Checks.
quelle
Sie wissen, ein neuer Treiber (zumindest unter Linux), der dazu in der Lage ist, wäre wahrscheinlich nicht so schwer zu schreiben.
Auf der anderen Seite wäre es töricht, Ihre Programme so zu erstellen. Ich würde es nicht empfehlen, es sei denn, Sie haben eine wirklich spezifische und einmalige Verwendung für so etwas. Wenn Sie eine große Anwendung erstellen, die mit konstanten Zeiger-Gültigkeitsprüfungen geladen ist, ist diese wahrscheinlich entsetzlich langsam.
quelle
Wenn sie nicht funktionieren - wird das nächste Windows-Update das Problem beheben? Wenn sie nicht auf Konzeptebene funktionieren, wird die Funktion wahrscheinlich vollständig aus der Windows-API entfernt.
In der MSDN-Dokumentation wird behauptet, dass sie verboten sind, und der Grund dafür ist wahrscheinlich ein Fehler bei der weiteren Gestaltung der Anwendung (z. B. sollten Sie im Allgemeinen keine ungültigen Zeiger stillschweigend essen - wenn Sie natürlich für die Gestaltung der gesamten Anwendung verantwortlich sind) und der Leistung / Zeit der Zeigerprüfung.
Sie sollten jedoch nicht behaupten, dass sie aufgrund eines Blogs nicht funktionieren. In meiner Testanwendung habe ich überprüft, ob sie funktionieren.
quelle
Diese Links können hilfreich sein
_CrtIsValidPointer Überprüft, ob ein angegebener Speicherbereich zum Lesen und Schreiben gültig ist (nur Debug-Version). http://msdn.microsoft.com/en-us/library/0w1ekd5e.aspx
_CrtCheckMemory Bestätigt die Integrität der im Debug-Heap zugewiesenen Speicherblöcke (nur Debug-Version). http://msdn.microsoft.com/en-us/library/e73x0s4b.aspx
quelle