Frage Testzeiger auf Gültigkeit (C / C ++)


Gibt es eine Möglichkeit, (natürlich programmatisch) zu bestimmen, ob ein gegebener Zeiger "gültig" ist? Die Suche nach NULL ist einfach, aber was ist mit 0x00001234? Wenn versucht wird, diese Art von Zeiger zu dereferenzieren, tritt eine Ausnahme / ein Absturz auf.

Eine plattformübergreifende Methode wird bevorzugt, aber plattformspezifisch (für Windows und Linux) ist auch in Ordnung.

Update zur Klärung: Das Problem ist nicht mit veralteten / freigegebenen / nicht initialisierten Zeigern; Stattdessen implementiere ich eine API, die Zeiger vom Aufrufer übernimmt (wie ein Zeiger auf eine Zeichenkette, eine Dateikennung usw.). Der Aufrufer kann einen ungültigen Wert als Zeiger senden (aus Versehen oder aus Versehen). Wie verhindere ich einen Absturz?


75
2018-02-15 15:45


Ursprung


Antworten:


Update zur Klärung: Das Problem besteht nicht in veralteten, freigegebenen oder nicht initialisierten Zeigern. Stattdessen implementiere ich eine API, die Zeiger vom Aufrufer übernimmt (wie ein Zeiger auf eine Zeichenkette, eine Dateikennung usw.). Der Aufrufer kann einen ungültigen Wert als Zeiger senden (aus Versehen oder aus Versehen). Wie verhindere ich einen Absturz?

Sie können diese Überprüfung nicht durchführen. Es gibt einfach keine Möglichkeit zu ü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 sie auf Adresse 0x4211 zeigt. Und wenn sie "versehentlich" ein Objekt treffen, dann würden Sie selbst dann, wenn Sie eine beängstigende Operationssystemfunktion (IsValidPtr oder was auch immer) verwenden würden, in einen Fehler schlüpfen und nicht schnell versagen.

Beginnen Sie mit Null-Zeigern für die Signalisierung dieser Art von Sache und sagen Sie dem Benutzer Ihrer Bibliothek, dass sie keine Zeiger verwenden sollten, wenn sie dazu neigen, ungültige Zeiger versehentlich zu übergeben.


68
2018-02-15 16:11



Das Verhindern eines Absturzes, der dadurch verursacht wird, dass der Aufrufer einen ungültigen Zeiger sendet, ist ein guter Weg, um stille Fehler zu finden, die schwer zu finden sind.

Ist es nicht besser für den Programmierer, der Ihre API verwendet, um eine klare Nachricht zu erhalten, dass sein Code gefälscht ist, indem er ihn abstürzt, anstatt ihn zu verstecken?


30
2018-02-15 16:05



Auf Win32 / 64 gibt es eine Möglichkeit, dies zu tun. Versuch, den Zeiger zu lesen und die resultierende SEH-Ausnahme abzufangen, die bei einem Fehler ausgelöst wird. Wenn es nicht wirft, dann ist es ein gültiger Zeiger.

Das Problem mit dieser Methode ist jedoch, dass es nur zurückgibt, ob Sie Daten aus dem Zeiger lesen können oder nicht. Es gibt keine Garantie für die Sicherheit des Typs oder eine Anzahl anderer Invarianten. Im Allgemeinen ist diese Methode gut für wenig anderes als zu sagen "Ja, ich kann diesen bestimmten Ort in Erinnerung zu einer Zeit lesen, die jetzt vergangen ist".

Kurz gesagt, tu das nicht;)

Raymond Chen hat einen Blogeintrag zu diesem Thema: http://blogs.msdn.com/oldnewthing/archiv/2007/06/25/3507294.aspx


26
2018-02-15 15:55



Hier sind drei einfache Möglichkeiten für ein C-Programm unter Linux, introvertiert zu werden über den Status des Speichers, in dem es läuft, und warum die Frage in einigen Kontexten entsprechende ausgeklügelte Antworten hat.

  1. Nach dem Aufruf von getpagesize () und Runden des Mauszeigers auf eine Seite Grenze, Sie können mincore () aufrufen, um herauszufinden, ob eine Seite gültig ist und wenn es zufällig Teil des Prozess-Arbeitssatzes ist. Beachten Sie, dass dies erforderlich ist einige Kernel-Ressourcen, so sollten Sie es Benchmark und bestimmen, ob Diese Funktion aufzurufen, ist in Ihrer API wirklich angemessen. Wenn deine api wird Interrupts behandeln oder von seriellen Ports lesen in Erinnerung, es ist angemessen, dies zu nennen, um unvorhersehbare zu vermeiden Verhaltensweisen.
  2. Nach dem Aufruf von stat (), um festzustellen, ob ein Verzeichnis / proc / self verfügbar ist, können Sie / proc / self / maps öffnen und durchlesen Informationen über die Region finden, in der sich ein Zeiger befindet. Untersuchen Sie die Manpage für proc, die Prozessinformations-Pseudodatei System. Offensichtlich ist das relativ teuer, aber Sie könnten sein in der Lage, das Ergebnis der Analyse in ein Array zwischenzuspeichern Sie können effizient mit einer binären Suche suchen. Beachten Sie auch die / proc / self / smaps. Wenn Ihre API dann für High-Performance-Computing ist Das Programm wird über das / proc / self / numa wissen wollen dokumentiert unter der man-Seite für numa, den nicht einheitlichen Speicher die Architektur.
  3. Der Aufruf get_meppolicy (MPOL_F_ADDR) eignet sich für Hochleistungs-Computing-API-Arbeiten, bei denen mehrere Threads von Ausführung und Sie verwalten Ihre Arbeit, um Affinität für nicht-einheitlichen Speicher zu haben wie es sich auf die CPU-Kerne und Socket-Ressourcen bezieht. Eine solche api wird Ihnen natürlich auch sagen, ob ein Zeiger gültig ist.

Unter Microsoft Windows gibt es die Funktion QueryWorkingSetEx, die unter der Process Status API (auch in der NUMA-API) dokumentiert ist. Als logische Konsequenz der anspruchsvollen NUMA-API-Programmierung ermöglicht Ihnen diese Funktion auch einfache "Testzeiger für Validität (C / C ++)", so dass es unwahrscheinlich ist, dass sie für mindestens 15 Jahre veraltet sind.


23
2018-05-12 17:22



AFAIK gibt es keinen Weg. Sie sollten versuchen, diese Situation zu vermeiden, indem Sie nach dem Freigeben von Speicher immer Zeiger auf NULL setzen.


15
2018-02-15 15:48



Schau es dir an Dies und Dies Frage. Siehe auch intelligente Zeiger.


7
2018-02-15 15:51



Zur Antwort ein bisschen in diesem Thread:

IsBadReadPtr (), IsBadWritePtr (), IsBadCodePtr (), IsBadStringPtr () für Windows.

Mein Rat ist, sich von ihnen fern zu halten, jemand hat diesen schon gepostet: http://blogs.msdn.com/oldnewthing/archiv/2007/06/25/3507294.aspx

Ein anderer Beitrag zum selben Thema und vom selben Autor (denke ich) ist dieser: http://blogs.msdn.com/oldnewthing/archiv/2006/09/27/773741.aspx ("IsBadXxxPtr sollte wirklich CrashProgramRandomly heißen").

Wenn die Benutzer Ihrer API fehlerhafte Daten senden, lassen Sie sie abstürzen. Wenn das Problem darin besteht, dass die übergebenen Daten erst später verwendet werden (und das Auffinden der Ursache erschwert wird), fügen Sie einen Debug-Modus hinzu, in dem die Strings usw. bei der Eingabe protokolliert werden. Wenn sie schlecht sind, wird es offensichtlich sein (und wahrscheinlich abstürzen). Wenn es zu oft passiert, lohnt es sich möglicherweise, Ihre API aus dem Prozess zu entfernen und sie den API-Prozess anstelle des Hauptprozesses abstürzen zu lassen.


7
2018-02-15 18:37



Erstens sehe ich keinen Sinn darin, sich vor dem Anrufer zu schützen, der absichtlich einen Absturz verursacht. 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 nur Ihr Gedächtnis oder den Stapel überschreiben. Wenn Sie sich vor so etwas schützen müssen, müssen Sie in einem separaten Prozess laufen, der Sockets oder einen anderen IPC für die Kommunikation verwendet.

Wir schreiben ziemlich viel Software, die es Partnern / Kunden / Benutzern ermöglicht, die Funktionalität zu erweitern. Zwangsläufig wird uns jeder Fehler gemeldet, daher ist es hilfreich, wenn Sie einfach zeigen können, dass das Problem im Plug-In-Code liegt. Darüber hinaus gibt es Sicherheitsbedenken und einige Benutzer sind vertrauenswürdiger als andere.

Je nach Performance / Durchsatzanforderungen und Vertrauenswürdigkeit verwenden wir verschiedene Methoden. Von den meisten bevorzugt:

  • separate Prozesse mit Sockets (die oft Daten als Text übergeben).

  • separate Prozesse mit gemeinsam genutztem Speicher (wenn große Datenmengen passieren).

  • gleicher Prozess separate Threads über Nachrichtenwarteschlange (wenn häufige Kurznachrichten).

  • gleicher Prozess separate Threads alle übergebenen Daten aus einem Speicherpool.

  • gleicher Prozess über direkten Prozeduraufruf - alle übergebenen Daten, die aus einem Speicherpool zugewiesen wurden.

Wir versuchen, niemals auf das zurückzugreifen, was Sie im Umgang mit Software von Drittanbietern tun wollen - vor allem, wenn wir die Plugins / Bibliothek als Binärcode und nicht als Quellcode erhalten.

Die Verwendung eines Speicherpools ist unter den meisten Umständen ziemlich einfach und muss nicht ineffizient sein. Wenn Sie die Daten an erster Stelle zuweisen, ist es trivial, die Zeiger auf die Werte zu überprüfen, die Sie zugewiesen haben. Sie können auch die zugewiesene Länge speichern und "magische" Werte vor und nach den Daten hinzufügen, um nach gültigen Datentypen und Datenüberschreitungen zu suchen.


6
2018-02-15 18:03



Ich habe viel Sympathie für Ihre Frage, da ich mich selbst in einer fast identischen Position befinde. Ich weiß zu schätzen, was viele der Antworten sagen, und sie sind richtig - die Routine, die den Zeiger liefert sollte einen gültigen Zeiger bereitstellen. In meinem Fall ist es fast unvorstellbar, dass sie den Zeiger korrumpiert haben könnten - aber wenn sie hätten geschafft, es wäre MEINE Software, die abstürzt, und ME, die die Schuld bekommen würde :-(

Meine Anforderung ist nicht, dass ich nach einem Segmentierungsfehler weitermache - das wäre gefährlich - ich möchte nur berichten, was dem Kunden passiert ist, bevor ich ihn beende, damit sie ihren Code reparieren können, anstatt mir die Schuld zu geben!

So habe ich es gefunden (unter Windows): http://www.cplusplus.com/reference/clibrary/csignal/signal/ 

Um eine Zusammenfassung zu geben:

#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);
}

Jetzt das erscheint so zu verhalten, wie ich es mir wünsche (es gibt die Fehlermeldung aus und beendet das Programm) - aber wenn jemand einen Fehler entdecken kann, lass es mich wissen!


4
2018-04-12 13:45



Es gibt keine Bestimmungen in C ++, um die Gültigkeit eines Zeigers als einen allgemeinen Fall zu testen. Man kann natürlich annehmen, dass NULL (0x00000000) schlecht ist, und verschiedene Compiler und Bibliotheken verwenden hier und da gerne "spezielle Werte", um das Debuggen zu vereinfachen (wenn ich z. B. einen Zeiger im visuellen Studio als 0xCECECECE sehe) Ich habe etwas falsch gemacht, aber die Wahrheit ist, dass, da ein Zeiger nur ein Index in den Speicher ist, es fast unmöglich ist, dies zu erkennen, indem man einfach auf den Zeiger schaut, wenn es der "richtige" Index ist.

Es gibt verschiedene Tricks, die Sie mit dynamic_cast und RTTI ausführen können, um sicherzustellen, dass das Objekt, auf das gezeigt wird, den gewünschten Typ aufweist, aber alle erfordern, dass Sie von vornherein auf etwas Gültiges zeigen.

Wenn Sie sicherstellen wollen, dass Ihr Programm "ungültige" Zeiger erkennt, dann ist mein Ratschlag: Setzen Sie jeden Zeiger, den Sie deklarieren, sofort nach der Erstellung auf NULL oder eine gültige Adresse und setzen Sie ihn sofort nach dem Freigeben des Speichers auf NULL. Wenn Sie mit dieser Übung fleißig sind, brauchen Sie nur nach NULL zu suchen.


2
2018-02-15 15:57



Es gibt keine portable Möglichkeit, dies zu tun, und dies für bestimmte Plattformen zu tun, kann irgendwo zwischen schwer und unmöglich sein. In jedem Fall sollten Sie niemals Code schreiben, der von einer solchen Prüfung abhängt - lassen Sie die Zeiger nicht von vornherein ungültige Werte annehmen.


2
2018-02-15 15:49