2011-01-25 3 views
12

मेरे पास कुछ सी ++ कोड (किसी और द्वारा लिखा गया) है जो गलत फ़ंक्शन को कॉल कर रहा है। यहां स्थिति है:सी ++ किसी ऑब्जेक्ट की पूरी तरह गलत (वर्चुअल) विधि को कॉल करना

UTF8InputStreamFromBuffer* cstream = foo(); 
wstring fn = L"foo"; 
DocumentReader* reader; 

if (a_condition_true_for_some_files_false_for_others) { 
    reader = (DocumentReader*) _new GoodDocumentReader(); 
} else { 
    reader = (DocumentReader*) _new BadDocumentReader(); 
} 

// the crash happens inside the following call 
// when a BadDocumentReader is used 
doc = reader->readDocument(*cstream, fn); 

जिन फ़ाइलों के लिए शर्त सही है, वे ठीक से संसाधित होते हैं; जिनके लिए यह झूठी दुर्घटना है। DocumentReader के लिए वर्ग पदानुक्रम इस तरह दिखता है:

class GenericDocumentReader { 
    virtual Document* readDocument(InputStream &strm, const wchar_t * filename) = 0; 
} 

class DocumentReader : public GenericDocumentReader { 
    virtual Document* readDocument(InputStream &strm, const wchar_t * filename) { 
     // some stuff 
    } 
}; 

class GoodDocumentReader : public DocumentReader { 
    Document* readDocument(InputStream & strm, const wchar_t * filename); 
} 

class BadDocumentReader : public DocumentReader { 
    virtual Document* readDocument(InputStream &stream, const wchar_t * filename); 
    virtual Document* readDocument(const LocatedString *source, const wchar_t * filename); 
    virtual Document* readDocument(const LocatedString *source, const wchar_t * filename, Symbol inputType); 
} 

निम्नलिखित भी प्रासंगिक हैं:

class UTF8InputStreamFromBuffer : public wistringstream { 
    // foo 
}; 
typedef std::basic_istream<wchar_t> InputStream; 

एक विज़ुअल सी में चल रहा है ++ डीबगर, यह पता चलता है कि एक BadDocumentReader पर readDocument कॉल

नहीं बुला रहा है
readDocument(InputStream&, const wchar_t*) 

बल्कि

readDocument(const LocatedString* source, const wchar_t *, Symbol) 

यह सभी पठन दस्तावेज़ों में कोउट स्टेटमेंट चिपके हुए द्वारा पुष्टि की जाती है। कॉल के बाद स्रोत तर्क निश्चित रूप से कचरे से भरा होता है, जो जल्द ही दुर्घटना का कारण बनता है। इनस्ट्रिंग में इनपुटस्ट्रीम से एक-तर्क निहित कन्स्ट्रक्टर होता है, लेकिन एक कोउट के साथ जांच से पता चलता है कि इसे कॉल नहीं किया जा रहा है। कोई विचार क्या यह समझा सकता है?

संपादित करें: अन्य संभवतः प्रासंगिक विवरण: दस्तावेज़ रीडर कक्षा कॉलिंग कोड की तुलना में एक अलग पुस्तकालय में हैं। मैंने सभी कोड का पूर्ण पुनर्निर्माण भी किया है और समस्या बनी रही है।

संपादित 2: मैं विज़ुअल सी ++ 2008

संपादित 3 उपयोग कर रहा हूँ: मैं एक ही व्यवहार के साथ एक "न्यूनतम compilable उदाहरण" बनाने की कोशिश की है, लेकिन समस्या को दोहराने में असमर्थ था।

संपादित 4:

बिली ONeal के सुझाव पर, मैं BadDocumentReader शीर्षक में readDocument तरीकों में से आदेश को बदलने की कोशिश की। निश्चित रूप से, जब मैं आदेश बदलता हूं, तो यह बदलता है कि कौन से कार्यों को बुलाया जाता है। ऐसा लगता है कि मेरे संदेह की पुष्टि करने के लिए vtable में अनुक्रमण के साथ कुछ अजीब चल रहा है, लेकिन मुझे यकीन नहीं है कि इसका क्या कारण है।

संपादित 5: यहाँ से पहले समारोह कॉल कुछ लाइनों के लिए disassembly है:

00559728 mov   edx,dword ptr [reader] 
0055972E mov   eax,dword ptr [edx] 
00559730 mov   ecx,dword ptr [reader] 
00559736 mov   edx,dword ptr [eax] 
00559738 call  edx 

मैं बहुत विधानसभा पता नहीं है, लेकिन जैसे कि यह पाठक चर सूचक dereferencing है यह मेरे लिए लग रहा है। स्मृति के इस हिस्से में संग्रहीत पहली चीज़ vtable के सूचक होने चाहिए, इसलिए यह eax में dereferences। फिर यह edx में vtable में पहले चीज़ रखता है और इसे कॉल करता है। विधियों के विभिन्न आदेशों के साथ पुन: संकलित करना इसे बदलना प्रतीत नहीं होता है। यह हमेशा vtable में पहली बात कॉल करना चाहता है। (मैं पूरी तरह से गलत समझा सकता था, असेंबली का कोई ज्ञान नहीं था ...)

आपकी मदद के लिए धन्यवाद।

संपादित करें 6: मुझे समस्या मिली, और मैं हर किसी के समय को बर्बाद करने के लिए क्षमा चाहता हूं।समस्या यह थी कि GoodDocumentReader को दस्तावेज़ रीडर के उप-वर्ग के रूप में घोषित किया जाना था, लेकिन वास्तव में नहीं था। सी-स्टाइल कास्ट संकलक त्रुटि को दबा रहा था (आपको सुनना चाहिए था, @ sellibitze, अगर आप एक उत्तर के रूप में अपनी टिप्पणी जमा करना चाहते हैं, तो मैं इसे सही के रूप में चिह्नित करूंगा)। मुश्किल बात यह है कि कोड कई दुर्घटनाओं तक शुद्ध दुर्घटना से काम कर रहा था जब तक किसी ने संशोधन नहीं किया, जब किसी ने गुड डॉक्यूमेंट रीडर को दो और वर्चुअल फ़ंक्शंस जोड़े, तो यह अब भाग्य से सही कार्य नहीं कर रहा था।

+0

'DefaultDocumentReader' क्या है? –

+2

क्या आप इसे कम से कम संकलित करने योग्य उदाहरण में घटा सकते हैं जो इस मुद्दे को प्रदर्शित करता है? –

+0

ओली: क्षमा करें, यह "BadDocumentReader" होना चाहिए। मैंने पोस्ट अपडेट किया है। –

उत्तर

2

मैं पहले सी-कास्ट को हटाने का प्रयास करता हूं।

  • यह पूरी तरह से अनावश्यक है, से एक एक बेस व्युत्पन्न भाषा में प्राकृतिक
  • यह, वास्तव में, एक बग के कारण हो सकता है (हालांकि यह माना जाता नहीं है)

ऐसा लग रहा है है डाले एक कंपाइलर बग ... यह निश्चित रूप से पहले वीएस में नहीं होगा।

मैं दुर्भाग्य से हाथ में 2008 नहीं है, जीसीसी में डाले सही ढंग से होते हैं:

struct Base1 
{ 
    virtual void foo() {} 
}; 

struct Base2 
{ 
    virtual void bar() {} 
}; 

struct Derived: Base1, Base2 
{ 
}; 

int main(int argc, char* argv[]) 
{ 
    Derived d; 
    Base1* b1 = (Base1*) &d; 
    Base2* b2 = (Base2*) &d; 

    std::cout << "Derived: " << &d << ", Base1: " << b1 
           << ", Base2: " << b2 << "\n"; 

    return 0; 
} 


> Derived: 0x7ffff1377e00, Base1: 0x7ffff1377e00, Base2: 0x7ffff1377e08 
11

ऐसा इसलिए हो रहा है क्योंकि विभिन्न स्रोत फ़ाइलें कक्षा के vtable लेआउट पर असहमत हैं। फ़ंक्शन को कॉल करने वाला कोड सोचता है कि readDocument(InputStream &, const wchar_t *) किसी विशेष ऑफसेट पर है, जबकि वास्तविक vtable में यह एक अलग ऑफसेट पर है।

यह आमतौर पर तब होता है जब आप vtable को बदलते हैं, जैसे कि उस वर्ग या उसके किसी भी मूल वर्ग में वर्चुअल विधि को जोड़कर या हटाकर, और फिर आप एक स्रोत फ़ाइल को पुन: संकलित करते हैं लेकिन अन्य स्रोत फ़ाइल नहीं। फिर, आपको असंगत ऑब्जेक्ट फ़ाइलें मिलती हैं, और जब आप उन्हें लिंक करते हैं, तो चीजें तेजी से बढ़ती हैं।

इसे ठीक करने के लिए, अपने सभी कोड का एक पूर्ण साफ और पुनर्निर्माण करें: लाइब्रेरी कोड और लाइब्रेरी का उपयोग करने वाला आपका कोड दोनों। यदि आपके पास लाइब्रेरी में स्रोत कोड नहीं है लेकिन आपके पास कक्षा परिभाषाओं के साथ हेडर फाइलें हैं, तो यह एक विकल्प नहीं है। उस स्थिति में, आप कक्षा परिभाषा को संशोधित नहीं कर सकते हैं - आपको इसे वापस लेना चाहिए कि यह आपको कैसे दिया गया था और आपके सभी कोड को फिर से सम्मिलित किया गया था।

+0

+1। .h फ़ाइल vtable सहित ऑब्जेक्ट के लेआउट को परिभाषित करती है; यदि आप पुस्तकालय को पुन: संकलित किए बिना इसे बदलते हैं, तो सब कुछ वास्तव में गड़बड़ हो जाएगा। –

+1

सिद्धांत रूप में, यह एक समस्या है यदि सूत्रों को या तो अलग संकलक, विभिन्न संस्करण या विभिन्न सेटिंग्स के साथ संकलित किया जाता है। लेकिन व्यावहारिक रूप से, इस मुद्दे के बारे में आने के लिए संकलक/विकल्पों को काफी अलग होना चाहिए (यह उल्लेख न करें कि निर्माण प्रणाली बहुत खराब है अगर यह पता नहीं लगा कि एक पुनर्मूल्यांकन की आवश्यकता है)। मुझे लगता है कि यह सी-स्टाइल कास्ट के साथ एक समस्या है जो सूचक मूल्य को सही ढंग से स्थानांतरित नहीं करती है (इसे वापस व्युत्पन्न वर्ग 'vtable से बेस क्लास' vtable में स्थानांतरित करें)। –

+0

@ मिकाएल पर्सन: कक्षा लेआउट बदलने के पहले मैं बहुत ही समान समस्याओं में भाग गया हूं, लेकिन इसका उपयोग करने वाली हर स्रोत फ़ाइल को पुन: संकलित नहीं किया गया था। ऐसा इसलिए हुआ क्योंकि संकलक झंडे का इस्तेमाल किया जा रहा था क्योंकि '-I- -Isomedir'' था, इसलिए जब निर्भरता '-एमएम' के साथ उत्पन्न हुई थी, तो 'somedir' में शीर्षलेख निर्भरता के रूप में सूचीबद्ध नहीं थे। –

0

असेंबली के आधार पर, यह बहुत स्पष्ट लगता है कि बाध्यकारी गतिशील है और vtable की पहली प्रविष्टि से है। सवाल यह है कि कौन सी वर्चुअल टेबल!? मैं सुझाव दूंगा कि आप सी-स्टाइल कास्ट के बजाय static_cast का उपयोग करें (बेशक, @VJo: dynamic_cast इस मामले में आवश्यक नहीं है!)। मानक में कुछ भी नहीं है जिसके लिए एक पॉइंटर BadDocumentReader* ptr की आवश्यकता होती है ताकि उसके वास्तविक मूल्य (पता) को static_cast<DocumentReader*>(ptr) के रूप में रखा जा सके। यह समझाएगा कि यह BadDocumentReader के vtable की पहली प्रविष्टि को कॉल क्यों करता है और इसकी बेस क्लास के vtable तक नहीं। और, बीटीडब्ल्यू, आपको इस मामले में बिल्कुल एक कलाकार की जरूरत नहीं है।

एक संभावना है कि वास्तव में एएसएम से सहमत नहीं है, लेकिन अभी भी जानना अच्छा है। क्योंकि आप BadDocumentReader को उसी दायरे में बनाते हैं क्योंकि आप reader->readDocument पर कॉल कर रहे हैं, तो कंपाइलर थोड़ा बहुत चालाक हो जाता है और निर्णय लेता है कि यह कॉल को गतिशील रूप से vtable में देखने के बिना हल कर सकता है। ऐसा इसलिए है क्योंकि यह जानता है कि पाठक सूचक का "वास्तविक" प्रकार वास्तव में BadDocumentReader है। तो यह vtable bipasses और कॉल स्थिर रूप से बांधता है। कम से कम, यह एक संभावना है जो लगभग एक समान स्थिति में मेरे साथ हुआ था। हालांकि, एएसएम के आधार पर, मुझे पूरा यकीन है कि पहली संभावना आपके मामले में होती है।

+0

सी स्टाइल कास्ट को पहले 'static_cast'' आज़माएं, इसलिए मुझे नहीं लगता कि यह क्या हो रहा है। –

0

मैं इस मुद्दे को और मेरे लिए समस्या यह है कि मैं इसे एक वर्ग के सदस्य चर में संग्रहीत किया गया था था। जब मैंने इसे एक पॉइंटर में बदल दिया और नया/हटाया, तो उसने सफलतापूर्वक बाल वर्ग और उसके कार्य को पंजीकृत किया।

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