2009-03-05 22 views
13

मैं एक पुस्तकालय लिख रहा हूं जिसे मैं पोर्टेबल बनाना चाहता हूं। इस प्रकार, यह glibc या Microsoft एक्सटेंशन या किसी अन्य चीज पर निर्भर नहीं होना चाहिए जो मानक में नहीं है। मेरे पास std :: अपवाद से व्युत्पन्न कक्षाओं का एक अच्छा पदानुक्रम है जिसका उपयोग मैं तर्क और इनपुट में त्रुटियों को संभालने के लिए करता हूं। यह जानकर कि किसी विशेष प्रकार के अपवाद को किसी विशेष फ़ाइल पर फेंक दिया गया था और लाइन नंबर उपयोगी है, लेकिन यह जानकर कि निष्पादन कैसे प्राप्त हुआ, वहां संभावित रूप से अधिक मूल्यवान होगा, इसलिए मैं स्टैक ट्रेस प्राप्त करने के तरीकों को देख रहा हूं।पोर्टेबल सी ++ अपवाद पर स्टैक ट्रेस

मुझे पता है कि इस डेटा को execinfo.h में कार्यों का उपयोग कर (question 76822 देखें) और माइक्रोसॉफ्ट के सी में StackWalk इंटरफेस के माध्यम से ++ कार्यान्वयन (question 126450 देखें) glibc के खिलाफ निर्माण उपलब्ध है हूँ, लेकिन मैं बहुत ज्यादा कुछ भी से बचने के लिए चाहते हैं वह पोर्टेबल नहीं है।

मैं इस रूप में इस कार्यक्षमता अपने आप को लागू करने की सोच रहा था:

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

यह एक भयानक विचार है? इसका मतलब यह होगा कि प्रत्येक कार्य में कोशिश/पकड़ ब्लॉक जोड़ने के बहुत सारे काम हैं, लेकिन मैं इसके साथ रह सकता हूं। यह तब काम नहीं करेगा जब अपवाद का कारण स्मृति भ्रष्टाचार या स्मृति की कमी है, लेकिन उस बिंदु पर आप वैसे भी बहुत खराब हो जाते हैं। यह भ्रामक जानकारी प्रदान कर सकता है अगर स्टैक में कुछ फ़ंक्शंस अपवाद नहीं लेते हैं, खुद को सूची में जोड़ते हैं, और फिर से प्रयास करते हैं, लेकिन मैं कम से कम गारंटी प्रदान कर सकता हूं कि मेरे सभी लाइब्रेरी फ़ंक्शन ऐसा करते हैं। एक "असली" स्टैक ट्रेस के विपरीत मुझे कॉलिंग फ़ंक्शंस में लाइन नंबर नहीं मिलेगा, लेकिन कम से कम मेरे पास कुछ होगा।

मेरी प्राथमिक चिंता यह संभावना है कि इससे कोई अपवाद नहीं होने पर भी मंदी का कारण बन जाएगा। क्या इन सभी प्रयास/पकड़ ब्लॉक को प्रत्येक फ़ंक्शन आमंत्रण पर अतिरिक्त सेट-अप और आंसू-डाउन की आवश्यकता होती है, या किसी भी तरह संकलन-समय पर संभाला जाता है? या क्या ऐसे अन्य मुद्दे हैं जिन पर मैंने विचार नहीं किया है?

उत्तर

21

मुझे लगता है कि यह वास्तव में एक बुरा विचार है।

पोर्टेबिलिटी एक बहुत ही योग्य लक्ष्य है, लेकिन जब यह घुसपैठ, प्रदर्शन-आकार और एक निम्न कार्यान्वयन के समाधान में नतीजा होता है।

प्रत्येक प्लेटफ़ॉर्म (विंडोज/लिनक्स/पीएस 2/आईफोन/आदि) मैंने काम किया है जब अपवाद होता है और फ़ंक्शन नामों के पते से मेल खाता है तो स्टैक पर चलने का एक तरीका प्रदान करता है। हां, इनमें से कोई भी पोर्टेबल नहीं है लेकिन रिपोर्टिंग ढांचा हो सकता है और यह आमतौर पर स्टैक पैदल कोड के प्लेटफॉर्म-विशिष्ट संस्करण को लिखने के लिए एक या दो दिन से भी कम समय लेता है।

क्रॉस-प्लेटफ़ॉर्म समाधान बनाने/बनाए रखने से यह कम समय नहीं है, लेकिन परिणाम बहुत बेहतर हैं;

  • मानक या तीसरे पक्ष के पुस्तकालयों
  • हर कार्य में आज़माएं/कैच के लिए कोई ज़रूरत नहीं (धीमी और स्मृति गहन)
2

मुझे नहीं लगता कि ऐसा करने के लिए "प्लेटफॉर्म स्वतंत्र" तरीका है - आखिरकार, यदि वहां था, तो स्टैकवॉक या विशेष जीसीसी स्टैक ट्रेसिंग सुविधाओं का उल्लेख करने की आवश्यकता नहीं होगी।

यह थोड़ा गन्दा होगा, लेकिन जिस तरह से मैं इसे लागू करूँगा वह एक वर्ग बनाना होगा जो स्टैक ट्रेस तक पहुंचने के लिए एक सतत इंटरफ़ेस प्रदान करता है, उसके बाद कार्यान्वयन में #ifdefs है जो उपयुक्त प्लेटफ़ॉर्म-विशिष्ट विधियों का उपयोग करता है वास्तव में एक साथ स्टैक ट्रेस डालने के लिए।

इस तरह कक्षा का आपका उपयोग प्लेटफ़ॉर्म स्वतंत्र है, और यदि आप किसी अन्य प्लेटफ़ॉर्म को लक्षित करना चाहते हैं तो बस उस वर्ग को संशोधित करने की आवश्यकता होगी।

0

यह हो जाएगा में कार्य

  • जाल दुर्घटनाओं को संशोधित करने की आवश्यकता नहीं है धीमा लेकिन ऐसा लगता है कि इसे काम करना चाहिए।

    एक तेज, पोर्टेबल, स्टैक ट्रेस बनाने में समस्या को समझने से मैं यह कहता हूं कि स्टैक कार्यान्वयन ओएस और सीपीयू दोनों विशिष्ट है, इसलिए यह निश्चित रूप से एक प्लेटफार्म विशिष्ट समस्या है। एक विकल्प एमएस/ग्लिबैक कार्यों का उपयोग करना होगा और विभिन्न बिल्डों में प्लेटफॉर्म विशिष्ट समाधानों को लागू करने के लिए #ifdef और उचित प्रीप्रोसेसर परिभाषित (उदा। _WIN32) का उपयोग करना होगा।

  • 0

    चूंकि ढेर का उपयोग अत्यधिक मंच और कार्यान्वयन निर्भर है, इसलिए इसे पूरी तरह से पोर्टेबल करने का कोई तरीका नहीं है। हालांकि, आप एक मंच और संकलक विशिष्ट कार्यान्वयन के लिए एक पोर्टेबल इंटरफ़ेस का निर्माण कर सकते हैं, जितना संभव हो सके मुद्दों को स्थानीयकृत कर सकते हैं। आईएमएचओ, यह आपका सबसे अच्छा तरीका होगा।

    ट्रैसर कार्यान्वयन तब भी प्लेटफ़ॉर्म विशिष्ट सहायक लाइब्रेरी उपलब्ध होने के लिए लिंक करेगा। यह तब तब होता है जब कोई अपवाद होता है, और तब भी तभी होता है जब आप इसे कैच ब्लॉक से बुलाते हैं। इसका न्यूनतम एपीआई बस पूरे ट्रेस वाली स्ट्रिंग को वापस कर देगा।

    कॉलर में कैच और रीथ्रो प्रोसेसिंग इंजेक्ट करने के लिए कोडर की आवश्यकता होती है, कुछ प्लेटफ़ॉर्म पर महत्वपूर्ण रनटाइम लागत होती है, और भविष्य में रखरखाव लागत को लागू करती है।

    यह कहा गया है कि, यदि आप कैच/थ्रो तंत्र का उपयोग करना चुनते हैं, तो यह न भूलें कि सी ++ में अभी भी सी प्रीप्रोसेसर उपलब्ध है, और मैक्रोज़ __FILE__ और __LINE__ परिभाषित किए गए हैं। आप उन्हें अपने ट्रेस जानकारी में स्रोत फ़ाइल नाम और लाइन नंबर शामिल करने के लिए उपयोग कर सकते हैं।

    6

    एक बार 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(); 
        } 
    } 
    

    ओवरहेड एनडीसी के कार्यान्वयन में है। मैं एक आलसी मूल्यांकन संस्करण के साथ-साथ एक के साथ खेल रहा था जो केवल एक निश्चित संख्या में प्रविष्टियां रखता था। मुख्य बिंदु यह है कि यदि आप ढेर को संभालने के लिए रचनाकारों और विनाशकों का उपयोग करते हैं ताकि आपको उन सभी try/catch ब्लॉक और हर जगह स्पष्ट हेरफेर की आवश्यकता न हो।

    एकमात्र प्लेटफ़ॉर्म विशिष्ट सिरदर्द getContextForCurrentThread() विधि है। यदि आप सभी मामलों में नौकरी को संभालने के लिए थ्रेड स्थानीय स्टोरेज का उपयोग करके प्लेटफॉर्म विशिष्ट कार्यान्वयन का उपयोग कर सकते हैं।

    आप उन्मुख होते हैं और अधिक प्रदर्शन और लॉग फाइल की दुनिया में रहते हैं, तो फ़ाइल नाम और लाइन नंबर करने के लिए एक सूचक धारण करने के लिए गुंजाइश बदल सकते हैं और एनडीसी बात को पूरी तरह छोड़ देते हैं:

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

    हो जाएगा ताकि अपवाद फेंकने पर आपको अपनी लॉग फ़ाइल में एक अच्छा स्टैक ट्रेस दें।किसी भी असली ढेर चलने के लिए कोई ज़रूरत नहीं, एक अपवाद फेंका जा रहा है जब बस थोड़ी लॉग संदेश;)

    +1

    एक और परिप्रेक्ष्य के लिए हर्ब सटर के http://www.gotw.ca/gotw/047.htm देखें। – AJG85

    1

    डिबगर में:

    जहां एक अपवाद फेंक मैं सिर्फ तोड़ stcik से है की स्टैक ट्रेस प्राप्त करने के लिए std :: अपवाद कन्स्ट्रक्टर में इंगित करें।

    इस प्रकार जब अपवाद बनाया जाता है तो डीबगर बंद हो जाता है और फिर आप उस बिंदु पर स्टैक ट्रेस देख सकते हैं। सही नहीं है लेकिन यह ज्यादातर समय काम करता है।

    +1

    वह डीबगर निर्देशों के लिए नहीं पूछ रहा था। –

    +1

    और यह डी-बगिंग में मदद नहीं करता है? एक उपयोगी टिप के लिए –

    +0

    +1 – Mawg

    1

    स्टैक प्रबंधन उन साधारण चीजों में से एक है जो बहुत जल्दी जटिल हो जाते हैं। इसे विशेष पुस्तकालयों के लिए बेहतर छोड़ दें। क्या आपने libunwind कोशिश की है? महान काम करता है और AFAIK यह पोर्टेबल है, हालांकि मैंने विंडोज़ पर कभी कोशिश नहीं की है।

    संबंधित मुद्दे