سؤال

أنا أكتب مكتبة أود أن تكون محمولة.وبالتالي، لا ينبغي أن يعتمد على ملحقات glibc أو Microsoft أو أي شيء آخر غير موجود في المعيار.لدي تسلسل هرمي لطيف للفئات المشتقة من std::exception الذي أستخدمه للتعامل مع الأخطاء في المنطق والإدخال.إن معرفة أنه تم طرح نوع معين من الاستثناءات على ملف معين ورقم سطر معين أمر مفيد، ولكن معرفة كيفية وصول التنفيذ إلى هناك قد يكون أكثر قيمة، لذلك كنت أبحث عن طرق للحصول على تتبع المكدس.

أدرك أن هذه البيانات متاحة عند البناء على glibc باستخدام الوظائف الموجودة في execinfo.h (انظر سؤال 76822) ومن خلال واجهة StackWalk في تطبيق Microsoft C++ (انظر السؤال 126450)، ولكنني أرغب بشدة في تجنب أي شيء غير قابل للنقل.

كنت أفكر في تنفيذ هذه الوظيفة بنفسي بهذا الشكل:

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

هل هذه فكرة رهيبة؟قد يعني ذلك الكثير من العمل من خلال إضافة كتل المحاولة/الالتقاط إلى كل وظيفة، لكن يمكنني التعايش مع ذلك.لن ينجح الأمر عندما يكون سبب الاستثناء هو تلف الذاكرة أو نقص الذاكرة، ولكن عند هذه النقطة تكون في وضع سيء إلى حد كبير على أي حال.قد توفر معلومات مضللة إذا لم تلتقط بعض الوظائف في المكدس الاستثناءات، وتضيف نفسها إلى القائمة، وتعيد طرحها، ولكن يمكنني على الأقل تقديم ضمان بأن جميع وظائف مكتبتي تفعل ذلك.على عكس تتبع المكدس "الحقيقي"، لن أحصل على رقم السطر في وظائف الاتصال، ولكن على الأقل سأحصل على شيء ما.

شاغلي الأساسي هو احتمال أن يؤدي هذا إلى التباطؤ حتى في حالة عدم وجود استثناءات فعليًا.هل تتطلب كل كتل المحاولة/الالتقاط هذه إعدادًا إضافيًا وتفكيكًا لكل استدعاء دالة، أم يتم التعامل معها بطريقة ما في وقت الترجمة؟أم أن هناك قضايا أخرى لم أفكر فيها؟

هل كانت مفيدة؟

المحلول

أعتقد أن هذه فكرة سيئة حقًا.

تعد قابلية النقل هدفًا قيمًا للغاية، ولكن ليس عندما تؤدي إلى حل تطفلي ومستنزف للأداء وتنفيذ رديء.

لقد قدمت كل منصة (Windows/Linux/PS2/iPhone/إلخ) التي عملت عليها طريقة للسير في المكدس عند حدوث استثناء ومطابقة العناوين بأسماء الوظائف.نعم، لا يمكن لأي من هذه العناصر أن يكون محمولاً، ولكن يمكن أن يكون إطار عمل إعداد التقارير كذلك، وعادةً ما يستغرق الأمر أقل من يوم أو يومين لكتابة إصدار خاص بالنظام الأساسي من التعليمات البرمجية للمشي المكدس.

ليس هذا وقتًا أقل مما قد يستغرقه إنشاء/صيانة حل مشترك بين الأنظمة الأساسية فحسب، بل إن النتائج أفضل بكثير؛

  • لا حاجة لتعديل الوظائف
  • تتعطل الملاءمات في المكتبات القياسية أو مكتبات الطرف الثالث
  • لا حاجة للمحاولة/الالتقاط في كل وظيفة (بطيئة ومكثفة للذاكرة)

نصائح أخرى

وابحث عن Nested Diagnostic Context مرة واحدة. هنا هو القليل من تلميح:

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

والنفقات العامة في تنفيذ NDC. كنت ألعب مع نسخة تقييم بتكاسل فضلا عن واحد التي أبقت فقط على عدد ثابت من إدخالات كذلك. والنقطة الأساسية هي أنه إذا كنت تستخدم الصانعين و destructors للتعامل مع كومة بحيث لا تحتاج كل هذه try / كتل catch سيئة والتلاعب واضح في كل مكان.

والصداع محدد المنبر الوحيد هو الأسلوب getContextForCurrentThread(). يمكنك استخدام تطبيق منصة محدد باستخدام موضوع التخزين المحلي للتعامل مع هذه المهمة في معظم إن لم يكن جميع الحالات.

إذا كنت أكثر أداء المنحى ويعيش في عالم ملفات السجل، ثم تغيير نطاق عقد المؤشر إلى اسم الملف ورقم السطر وحذف شيء NDC تماما:

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

وهذا سوف اعطيكم تتبع المكدس لطيفة في ملف السجل الخاص بك عندما يتم طرح استثناء. لا حاجة لأي المشي كومة الحقيقي، مجرد رسالة سجل قليلا عندما يتم طرح استثناء.)

وأنا لا أعتقد أن هناك وسيلة "منصة مستقلة" للقيام بذلك - بعد كل شيء، إذا كان هناك، لن تكون هناك حاجة لStackWalk أو كومة دول مجلس التعاون الخليجي خاصة تتبع ملامح أذكر لكم

وسيكون فوضوي قليلا، ولكن الطريقة أود أن تنفيذ هذا سيكون لإنشاء فئة التي توفر واجهة متناسقة للوصول إلى تتبع المكدس، ثم يكون #ifdefs في تنفيذ ذلك استخدام الأساليب المناسبة الخاصة بالنظام الأساسي ل فعلا وضع تتبع المكدس معا.

وبهذه الطريقة استخدامك للطبقة هو منصة مستقلة، وفقط تلك الفئة سوف تحتاج إلى تعديل إذا أردت لاستهداف بعض منصة أخرى.

في المصحح:

لتحصل على تتبع المكدس من حيث استثناء ورمي من أنا فقط stcik نقطة فاصل في منشئ الأمراض المنقولة جنسيا :: استثناء.

وهكذا عندما يتم إنشاء استثناء توقف المصحح ويمكنك بعد ذلك نرى تتبع المكدس في تلك المرحلة. لم يكن مثاليا ولكنه يعمل معظم الوقت.

والمكدس إدارة واحدة من تلك الأشياء البسيطة التي تتعقد بسرعة كبيرة. ترك ذلك أفضل للمكتبات المتخصصة. هل حاولت libunwind؟ يعمل كبيرة وAFAIK انها المحمولة، على الرغم من أنني قد حاولت ذلك أبدا على ويندوز.

وهذا سوف يكون أبطأ ولكن يبدو أنه يجب أن تعمل.

وحسب ما فهمت المشكلة في صنع سريع، والمحمولة، تتبع المكدس هو أن تنفيذ كومة على حد سواء OS وحدة المعالجة المركزية محددة، لذلك هو ضمنا مشكلة معينة المنصة. فإن البديل يتمثل في استخدام وظائف MS / سي العمومية واستخدام #IFDEF والمعالج المناسب يعرف (على سبيل المثال _WIN32) لتنفيذ حلول محددة منصة في مختلف يبني.

ومنذ استخدام المكدس هو غاية منصة وتنفيذ التابع، لا توجد وسيلة للقيام بذلك مباشرة وهذا هو محمول تماما. ومع ذلك، هل يمكن بناء واجهة المحمولة إلى منصة ومترجم محددة للتنفيذ، إضفاء الطابع المحلي على القضايا قدر الإمكان. IMHO، وهذا سيكون نهج افضل ما لديكم.

وتنفيذ التتبع بعد ذلك يرتبط مهما منصة والمكتبات المساعد محددة المتاحة. بعد ذلك أنها تعمل فقط عند حدوث استثناء، وحتى ذلك الحين إلا إذا كنت يطلق عليه من كتلة الصيد. سيكون لها API الحد الأدنى من العودة ببساطة سلسلة تحتوي على أثر كله.

وإلزام المبرمج لحقن الصيد وتجهيز rethrow في سلسلة دعوة لديها تكاليف وقت هامة على بعض الأنظمة الأساسية، ويفرض تكاليف الصيانة كبيرة في المستقبل.

ومع ذلك، إذا كنت قد اخترت لاستخدام آلية الصيد / رمي، لا ننسى أنه حتى C ++ لا تزال لديه المعالج C المتاحة، والتي تم تعريفها في __FILE__ وحدات الماكرو و__LINE__. يمكنك استخدامها لتضمين اسم الملف المصدر ورقم السطر في معلومات التتبع الخاص بك.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top