Frage

Ich schreibe eine Bibliothek, die ich tragbar sein möchte. So soll es nicht auf glibc oder Microsoft-Erweiterungen oder irgendetwas anderes abhängen, die nicht in dem Standard ist. Ich habe eine schöne Hierarchie von Klassen abgeleitet von std :: Ausnahme, dass ich Fehler in der Logik und Eingabe verwenden zu handhaben. Zu wissen, dass eine bestimmte Art von Ausnahme bei einer bestimmten Datei und die Zeilennummer geworfen wurde, ist nützlich, aber zu wissen, wie die Ausführung dort bekam potenziell viel mehr wert sein würde, also habe ich nach Möglichkeiten für den Erwerb des Stack-Trace an.

Ich bin mir bewusst, dass diese Daten zur Verfügung, wenn für glibc der Funktionen in execinfo.h (siehe Frage 76.822 ) und durch die StackWalk Schnittstelle in Microsoft C ++ Implementierung (siehe Frage 126.450 ), aber ich mag sehr viel, alles zu vermeiden, die nicht tragbar ist.

Ich dachte an dieser Funktionalität selbst in dieser Form der Umsetzung:

class myException : public std::exception
{
public:
  ...
  void AddCall( std::string s )
  { m_vCallStack.push_back( s ); }
  std::string ToStr() const
  {
    std::string l_sRet = "";
    ...
    l_sRet += "Call stack:\n";
    for( int i = 0; i < m_vCallStack.size(); i++ )
      l_sRet += "  " + m_vCallStack[i] + "\n";
    ...
    return l_sRet;
  }
private:
  ...
  std::vector< std::string > m_vCallStack;
};

ret_type some_function( param_1, param_2, param_3 )
{
  try
  {
    ...
  }
  catch( myException e )
  {
    e.AddCall( "some_function( " + param_1 + ", " + param_2 + ", " + param_3 + " )" );
    throw e;
  }
}

int main( int argc, char * argv[] )
{
  try
  {
    ...
  }
  catch ( myException e )
  {
    std::cerr << "Caught exception: \n" << e.ToStr();
    return 1;
  }
  return 0;
}

Ist das eine schreckliche Idee? Es würde eine Menge Arbeit Hinzufügen try / catch-Blöcke zu jeder Funktion bedeuten, aber ich kann damit leben. Es würde nicht funktionieren, wenn die Ursache für die Ausnahme-Speicher ist Korruption oder fehlende Erinnerung, aber an diesem Punkt Sie sowieso ziemlich geschraubt werden. Es kann irreführende Informationen zur Verfügung stellen, wenn einige Funktionen in dem Stapel nicht Ausnahmen fangen, fügen sich in die Liste, und rethrow, aber ich kann eine Garantie zumindest vor, dass alle meine Bibliotheksfunktionen so zu tun. Im Gegensatz zu einem „echten“ Stack-Trace Ich werde die Zeilennummer in den Anruffunktionen nicht, aber zumindest würde ich etwas hat.

Mein Hauptanliegen ist die Möglichkeit, dass dies auch eine Verlangsamung verursachen wird, wenn keine Ausnahmen tatsächlich geworfen werden. Sind alle diese try / catch-Blöcke erfordern eine zusätzliche Set-up und tear-down auf jedem Funktionsaufruf, oder irgendwie zur Compile-Zeit behandelt? Oder gibt es andere Probleme habe ich nicht in Betracht gezogen?

War es hilfreich?

Lösung

Ich denke, das eine wirklich schlechte Idee ist.

Portabilität ist ein sehr erstrebenswertes Ziel, aber nicht, wenn es führt zu einer Lösung, die aufdringlich, leistungszehrende ist, und eine minderwertige Umsetzung.

Jede Plattform (Windows / Linux / PS2 / iPhone / etc) Ich gearbeitet habe hat einen Weg angeboten, den Stapel zu gehen, wenn eine Ausnahme auftritt und Spiel Adressen Namen zu funktionieren. Ja, keiner von ihnen sind tragbar, aber die Berichtsrahmen kann sein, und es dauert in der Regel weniger als ein oder zwei Tage eine plattformspezifische Version von Stapel zu Fuß Code zu schreiben.

ist nicht nur das weniger Zeit, als es dauern würde erstellen / Cross-Plattform-Lösung beibehalten, aber die Ergebnisse sind viel besser;

  • Keine Notwendigkeit zu ändern Funktionen
  • Siphons Abstürze in Standard- oder Bibliotheken von Drittanbietern
  • Keine Notwendigkeit für ein try / catch in jeder Funktion (langsam und speicherintensiv)

Andere Tipps

nachschlagen Nested Diagnostic Context einmal. Hier ist ein kleiner Hinweis:

class NDC {
public:
    static NDC* getContextForCurrentThread();
    int addEntry(char const* file, unsigned lineNo);
    void removeEntry(int key);
    void dump(std::ostream& os);
    void clear();
};

class Scope {
public:
    Scope(char const *file, unsigned lineNo) {
       NDC *ctx = NDC::getContextForCurrentThread();
       myKey = ctx->addEntry(file,lineNo);
    }
    ~Scope() {
       if (!std::uncaught_exception()) {
           NDC *ctx = NDC::getContextForCurrentThread();
           ctx->removeEntry(myKey);
       }
    }
private:
    int myKey;
};
#define DECLARE_NDC() Scope s__(__FILE__,__LINE__)

void f() {
    DECLARE_NDC(); // always declare the scope
    // only use try/catch when you want to handle an exception
    // and dump the stack
    try {
       // do stuff in here
    } catch (...) {
       NDC* ctx = NDC::getContextForCurrentThread();
       ctx->dump(std::cerr);
       ctx->clear();
    }
}

Der Aufwand ist bei der Umsetzung des NDC. Ich war mit einer lazily ausgewertet Version spielen sowie einer, die nur als auch eine feste Anzahl von Einträgen gehalten. Der entscheidende Punkt ist, dass wenn Sie Konstruktoren und Destruktoren verwenden den Stapel zu verarbeiten, so dass Sie nicht brauchen alle diese bösen try / catch Blöcke und explizite Manipulation überall.

Der einzige Plattform-spezifische Kopfschmerz ist die getContextForCurrentThread() Methode. Sie können eine plattformspezifische Implementierung mit lokalen Threadspeicher verwenden, um den Job in den meisten, wenn nicht alle Fälle zu behandeln.

Wenn Sie mehr leistungsorientiert und leben in der Welt der Protokolldateien sind, dann den Umfang ändern, um einen Zeiger auf den Dateinamen und die Zeilennummer zu halten und die NDC Sache auslassen insgesamt:

class Scope {
public:
    Scope(char const* f, unsigned l): fileName(f), lineNo(l) {}
    ~Scope() {
        if (std::uncaught_exception()) {
            log_error("%s(%u): stack unwind due to exception\n",
                      fileName, lineNo);
        }
    }
private:
    char const* fileName;
    unsigned lineNo;
};

Dies gibt Ihnen einen schönen Stack-Trace in der Protokolldatei, wenn eine Ausnahme ausgelöst wird. Keine Notwendigkeit für einen echten Stack-Walking, nur eine wenig Protokollmeldung, wenn eine Ausnahme ausgelöst wird;)

Das glaube ich nicht, dass es eine „plattformunabhängig“ Art und Weise, dies zu tun -. Nach allem, wenn es, gäbe es war kein Bedürfnis nach StackWalk oder dem speziell gcc Stack Tracing-Funktionen, die Sie erwähnen

Es wäre ein bisschen chaotisch sein, aber die Art, wie ich dies umsetzen würde wäre, eine Klasse zu erstellen, die für den Zugriff auf den Stack-Trace eine konsistente Schnittstelle bietet, dann #ifdefs bei der Umsetzung hat, die die entsprechenden plattformspezifischen Methoden verwenden, um setzen Sie den Stack-Trace tatsächlich zusammen.

Auf diese Weise Ihrer Nutzung der Klasse plattformunabhängig ist, und nur diese Klasse müßte geändert werden, wenn Sie eine andere Plattform zielen wollen.

Im Debugger:

Um den Stack-Trace, wo eine Ausnahme wirft aus ich nur den Bruchpunkt in Konstruktor std :: exception Stcik.

Wenn also die Ausnahme ist der Debugger stoppt erstellt und Sie können dann den Stack-Trace an diesem Punkt sehen. Nicht perfekt, aber es funktioniert die meiste Zeit.

Stapel Verwaltung ist ein jene einfachen Dinge, die sehr schnell kompliziert werden. Es ist besser für Fachbibliotheken. Haben Sie versucht, libunwind? Funktioniert prima und es ist AFAIK tragbar, obwohl ich es nie auf Windows versucht haben.

Dies wird langsamer sein, aber sieht aus wie es funktionieren soll.

Von dem, was ich verstehe das Problem bei der Herstellung eines schnell, tragbar, Stack-Trace ist, dass die Stack-Implementierung ist sowohl O und CPU spezifisch, so ist es implizit eine Plattform-spezifisches Problem. Eine Alternative wäre es, die MS / glibc Funktionen zu verwenden und #ifdef und geeignete Präprozessor definiert (z.B. _WIN32) zu verwenden, die plattformspezifische Lösungen zu implementieren, in verschiedenen aufbaut.

Da Stackverbrauch hoch Plattform ist und die Umsetzung abhängig, gibt es keine Möglichkeit, es zu tun direkt das vollständig tragbar ist. Sie können jedoch eine tragbare Schnittstelle zu einer Plattform und Compiler-spezifischen Implementierung bauen, die Probleme so weit wie möglich zu lokalisieren. IMHO, dies wäre die beste Ansatz.

Der Tracer Implementierung würde dann eine Verknüpfung zu, was auch immer plattformspezifische Helfer Bibliotheken zur Verfügung stehen. Es wäre dann nur betrieben werden, wenn eine Ausnahme auftritt, und auch nur dann, wenn Sie es von einem catch-Block genannt. Seine minimale API würde einfach einen String zurückgeben, die ganze Spur enthält.

Die Forderung an die Coder Fang- und rethrow Verarbeitung in der Call-Kette zu injizieren, hat erhebliche Laufzeitkosten auf einigen Plattformen und erlegt eine große Zukunft Wartungskosten.

Das hieß, wenn Sie den catch / throw-Mechanismus verwenden wählen, vergessen Sie nicht, dass auch C ++ noch den C-Präprozessor zur Verfügung hat, und dass die Makros __FILE__ und __LINE__ definiert. Sie können sie verwenden, um die Quelldateinamen und die Zeilennummer in der Trace-Informationen enthalten.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top