2010-05-24 12 views
6

स्थिति निम्न है। मैं पुस्तकालय साझा किया है, जो वर्ग परिभाषा शामिल हैं -हेडर में परिभाषित सी ++ वर्चुअल फ़ंक्शन को संकलित और vtable में लिंक क्यों नहीं किया जा सकता है?

QueueClass : IClassInterface 
{ 
    virtual void LOL() { do some magic} 
} 

मेरे साझा लाइब्रेरी प्रारंभ वर्ग के सदस्य

QueueClass *globalMember = new QueueClass(); 

मेरे शेयर पुस्तकालय निर्यात सी समारोह जो globalMember सूचक रिटर्न -

void * getGlobalMember(void) { return globalMember;} 

मेरे एप्लिकेशन ग्लोबल मेम्बर का उपयोग करता है जैसे

((IClassInterface*)getGlobalMember())->LOL(); 

अब बहुत उबेर सामान - यदि मैं साझा लाइब्रेरी से एलओएल का संदर्भ नहीं देता हूं, तो एलओएल लिंक नहीं है और इसे एप्लिकेशन से कॉल अप अपवाद बढ़ाता है। कारण - VTABLE में एलओएल() फ़ंक्शन के सूचक के स्थान पर नूल होता है।

जब मैं .h फ़ाइल से .cpp तक LOL() परिभाषा को स्थानांतरित करता हूं, अचानक यह VTABLE में दिखाई देता है और सब कुछ बहुत अच्छा काम करता है। इस व्यवहार को क्या बताता है ?! (जीसीसी कंपाइलर + एआरएम आर्किटेक्चर_)

+0

'क्यूई क्लास' (यदि कोई है तो) में किसी अन्य गैर-इनलाइन वर्चुअल फ़ंक्शंस के बारे में क्या? क्या वे कार्य करते हैं? दूसरे शब्दों में, 'एलओएल' स्थानीय 'एलओएल की वीटेबल एंट्री' के साथ मुद्दा है या पूरा वीटीई खाली या गायब है? – AnT

+0

आईसीएलएएस इंटरफेस से निजी तौर पर क्यों उत्तराधिकारी है? –

+2

'आईसीएलएएस इंटरफेस *' के बजाय 'शून्य ...' फ़ंक्शन 'शून्य *' क्यों लौटा रहा है? – AnT

उत्तर

6

लिंकर यहां अपराधी है। जब कोई फ़ंक्शन इनलाइन होता है तो इसमें कई परिभाषाएं होती हैं, प्रत्येक सीपीपी फ़ाइल में से एक जहां इसे संदर्भित किया जाता है। यदि आपका कोड कभी फ़ंक्शन का संदर्भ नहीं देता है तो यह कभी उत्पन्न नहीं होता है।

हालांकि, vtable लेआउट कक्षा परिभाषा के साथ संकलन समय पर निर्धारित किया गया है। संकलक आसानी से बता सकता है कि LOL() एक वर्चुअल फ़ंक्शन है और vtable में प्रवेश करने की आवश्यकता है।

जब यह ऐप के लिए समय लिंक करने के लिए मिलता है तो यह QueueClass::_VTABLE के सभी मानों को भरने का प्रयास करता है लेकिन LOL() की परिभाषा नहीं पाता है और इसे खाली (शून्य) छोड़ देता है।

समाधान साझा पुस्तकालय में फ़ाइल में LOL() संदर्भित करना है। &QueueClass::LOL; के रूप में सरल कुछ। संकलक को बिना किसी प्रभाव के शिकायत करना बंद करने के लिए आपको इसे फेंकने वाले चर पर असाइन करने की आवश्यकता हो सकती है।

+0

इसका मतलब है कि कंपाइलर + लिंकर सिर्फ पागल हैं! उन्हें आपको रन-टाइम पर एक जीवित वस्तु बनाने की अनुमति नहीं देनी चाहिए जिसका वर्चुअल टेबल पूरी तरह परिभाषित नहीं है। ऐसे मामले में लिंकर "अनसुलझा" त्रुटि उत्पन्न कर सकता है – valdo

+0

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

+0

'अनसुलझा' आमतौर पर जेनरेट किया जाता है जब लाइब्रेरी लोड होती है, न कि जब यह बनाई जाती है, ताकि आपके पास दूसरी लाइब्रेरी से प्रतीकों का संदर्भ देने की संभावना हो। –

1

मेरा अनुमान है कि जीसीसी कॉल को एलओएल में इनलाइन करने का अवसर ले रहा है। मैं देखूंगा कि क्या मैं इस पर आपके लिए एक संदर्भ पा सकता हूं ...

मुझे लगता है कि sechastain मुझे एक और अधिक विस्तृत विवरण में हराया और मैं जिस संदर्भ को ढूंढ रहा था उसे मैं Google नहीं कर सका। तो मैं इसे छोड़ दूंगा।

+0

हाँ, यदि जीसीसी में किसी भी गैर-इनलाइन, कक्षा में वर्चुअल फ़ंक्शन नहीं दिखते हैं, तो यह इसके लिए vtable को उत्सर्जित नहीं करेगा । http://gcc.gnu.org/faq।एचटीएमएल # vtables –

+0

धन्यवाद, यह मदद करता है। मुझे बस इस मुद्दे पर वास्तव में एक अच्छा लेखन लिखना याद है, लेकिन इस समय तेह ग्रेट गिज़ोगल मुझे असफल कर रहा है क्योंकि मैं इसे खोजने के लिए कीवर्ड के सही संयोजन के साथ नहीं आ सकता। यह काफी बुरा है कि मैं चीजों को अपने असली सिर में नहीं रख सकता, जब मेरा वर्चुअल मस्तिष्क भी कमजोर होता है, जो मुझे बहुत खेदजनक स्थिति में छोड़ देता है ;-) – Ukko

+0

हुहू, मुझे इस के साथ जवाब देने के लिए मेरा पहला नकारात्मक भी मिला ! मैं मानता हूं कि यह मेरा सबसे अच्छा काम नहीं था, और +1 के इच्छुक लोगों के लिए -1 के बाद नेट + नेट किया गया। मैंने हमेशा सोचा कि क्या यह इस तरह से काम करेगा या यदि आप मूल +10 खो देंगे, तो अब हम जानते हैं ;-) – Ukko

0

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

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

मुझे यकीन नहीं है कि यह आपके हिस्से पर जल्दबाजी कर रहा है, लेकिन प्रदान किए गए कोड से आईसीएलएएस इंटरफेस में एलओएल, केवल क्यूई क्लास शामिल नहीं है। लेकिन आप एलओएल कॉल करने के लिए आईसीएलएएस इंटरफेस पॉइंटर पर कास्टिंग कर रहे हैं।

+3

यह फर्जी तर्क है। सिर्फ इसलिए कि एक फ़ंक्शन इनलाइन है इसका मतलब यह नहीं है कि आप इसका पता नहीं ले सकते हैं। "यह क्या इंगित करेगा" संकलक की समस्या उपयोगकर्ता की समस्या नहीं है। आम तौर पर, कंपाइलर फ़ंक्शन के लिए एक स्टैंडअलोन बॉडी उत्पन्न करते हैं। इस मामले में यह भी होना चाहिए था। दूसरे शब्दों में, कोड/दृष्टिकोण के साथ कोई समस्या नहीं है। – AnT

+0

वैसे यह पूरी तरह से फर्जी नहीं है। जब आप हेडर में कोड डालने वाले सी दिनों में वापस जाते हैं तो आपदा के लिए कुल नुस्खा था, चीजें अब बेहतर होती हैं; हालांकि, अभी भी समस्याएं हैं जहां संकलक को उस कोड को उत्सर्जित करना चाहिए। हेडर फ़ाइल समेत आपके मॉडल के तहत कई विधियों के एक गैर-इनलाइन संस्करण को उत्सर्जित करने का परिणाम होगा, जिसके परिणामस्वरूप बड़ी ऑब्जेक्ट फ़ाइलें और लिंकर के बहुत सारे काम लाभ के लिए नहीं होंगे। (यह एक बड़ी समस्या है जब टेम्पलेट्स शामिल हैं।) यह सब फ़ाइल आधारित अलग संकलन की मूल समस्या से आता है - समझौता किया गया था, माफ करना 'मुकाबला। – Ukko

+0

मुझे एक 'शून्य *' से गुजरना पड़ता है और काफी समस्या का सामना करना पड़ता है, खासकर जब कास्ट की श्रृंखला दी जाती है: 'क्यूई क्लास * '->' शून्य * '->' आईसीएलएएस इंटरफेस * ';) –

0

यदि यह उदाहरण सरलीकृत है, और आपका वास्तविक विरासत पेड़ एकाधिक विरासत का उपयोग करता है, तो इसे आसानी से समझाया जा सकता है। जब आप ऑब्जेक्ट पॉइंटर पर टाइपकास्ट करते हैं, तो कंपाइलर को पॉइंटर समायोजित करने की आवश्यकता होती है ताकि उचित Vtable संदर्भित किया जा सके। चूंकि आप void * लौट रहे हैं, इसलिए कंपाइलर में समायोजन करने के लिए आवश्यक जानकारी नहीं है।

संपादित करें: वहाँ सी ++ वस्तु लेआउट के लिए कोई मानक नहीं है, लेकिन कैसे एकाधिक वंशानुक्रम काम हो सकता है Bjarne Stroustrup खुद का यह लेख देखें में से एक उदाहरण के लिए: http://www-plan.cs.colorado.edu/diwan/class-papers/mi.pdf

अगर यह वास्तव में आपकी समस्या है, तो आप हो सकता है एक साधारण परिवर्तन के साथ इसे ठीक करने में सक्षम:

IClassInterface *globalMember = new QueueClass(); 

C++ कम्पाइलर आवश्यक सूचक संशोधन कर जब यह, काम करता है ताकि सी समारोह सही सूचक लौट सकते हैं।

+0

एकाधिक विरासत कैसे काम करती है असली दुनिया? Vtables कैसे बनाए जाते हैं? कहें - वर्ग एकाधिक: इंटरफ़ेस 1, इंटरफ़ेस 2; इस राक्षस के लिए vtable कैसे देखेंगे ??? मैं वास्तव में उलझन में हूँ। –

+0

@ 0xDEAD BEEF, मेरा अपडेट देखें। –

2

मैं @sechastain से असहमत हूं।

इनलाइनिंग स्वचालित होने से बहुत दूर है। विधि या संकेत (inline कीवर्ड या __forceinline) में विधि को परिभाषित किया गया है या नहीं, इसका उपयोग किया जाता है, यह तय करने के लिए संकलक केवल एकमात्र है कि इनलाइनिंग वास्तव में होगी या नहीं, और ऐसा करने के लिए जटिल हेरिस्टिक का उपयोग करता है। हालांकि, एक विशेष मामला यह है कि जब रनटाइम प्रेषण का उपयोग करके वर्चुअल विधि का आह्वान किया जाता है तो यह कॉल को इनलाइन नहीं करेगा, ठीक है क्योंकि रनटाइम प्रेषण और इनलाइनिंग संगत नहीं है।

IClassInterface* i = /**/; 
i->LOL();     // runtime dispatch 
i->QueueClass::LOL();  // compile time dispatch, inline is possible 

@0xDEAD BEEF:

"क्रम प्रेषण का उपयोग कर" की शुद्धता को समझने के लिए मैं अपने डिजाइन कम से कम कहने के लिए भंगुर पाते हैं।

सी-शैली के उपयोग यहाँ डाले गलत है:

QueueClass* p = /**/; 
IClassInterface* q = p; 

assert(((void*)p) == ((void*)q)); // may fire or not... 

मूलरूप कोई गारंटी नहीं कि 2 पतों बराबर हैं है: यह कार्यान्वयन परिभाषित है, और परिवर्तन का विरोध करने की संभावना नहीं है।

मैं तुम्हें तो आप एक IClassInterface* से उसे बनाने के लिए मूल रूप से इतना है कि सी ++ संकलक वस्तुओं के लेआउट पर निर्भर सही सूचक अंकगणित प्रदर्शन कर सकते हैं की जरूरत है सुरक्षित रूप से एक IClassInterface* सूचक को void* सूचक कास्ट करने के लिए सक्षम होने के लिए चाहते हैं।

बेशक, मैं वैश्विक चर के उपयोग की तुलना में भी रेखांकित करूँगा ... आप शायद इसे जानते हैं।

अनुपस्थिति के कारण के रूप में? मैं ईमानदारी से कंपाइलर/लिंकर में एक बग से अलग नहीं देखता हूं। मैंने virtual की अंतर्निहित परिभाषा को कुछ बार देखा है (अधिक विशेष रूप से, clone विधि) और इससे कभी भी समस्याएं नहीं हुईं।

संपादित: चूंकि "सही सूचक अंकगणित" इतनी अच्छी तरह से समझा नहीं गया था, यहां एक उदाहरण

struct Base1 { char mDum1; }; 

struct Base2 { char mDum2; }; 

struct Derived: Base1, Base2 {}; 

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

    std::cout << "Base1: " << b1 
      << "\nBase2: " << b2 
      << "\nDerived: " << &d << std::endl; 

    return 0; 
} 

यहाँ है और क्या छपा था है:

Base1: 0x7fbfffee60 
Base2: 0x7fbfffee61 
Derived: 0x7fbfffee60 

नहीं के बीच का अंतर b2 और &d का मान, भले ही वे एक इकाई का संदर्भ लें। यह समझा जा सकता है अगर कोई ऑब्जेक्ट के मेमोरी लेआउट के बारे में सोचता है।

Derived 
Base1  Base2 
+-------+-------+ 
| mDum1 | mDum2 | 
+-------+-------+ 

जब Base2* को Derived* से परिवर्तित, संकलक (एक बाइट द्वारा सूचक पता यहाँ, बढ़ाने के) आवश्यक समायोजन प्रदर्शन करेंगे ताकि सूचक को प्रभावी ढंग से Derived की Base2 भाग की ओर इशारा करते समाप्त होता है और के लिए नहीं Base1 भाग को गलती से Base2 ऑब्जेक्ट (जो कि बुरा होगा) के रूप में व्याख्या की गई है।

यही कारण है कि डाउनकास्टिंग के दौरान सी-स्टाइल कास्ट का उपयोग करना टालना चाहिए। यहां, यदि आपके पास Base2 पॉइंटर है तो आप इसे Derived पॉइंटर के रूप में पुन: परिभाषित नहीं कर सकते हैं। इसके बजाय, आपको static_cast<Derived*>(b2) का उपयोग करना होगा जो सूचक को एक बाइट द्वारा कम करेगा ताकि यह सही ढंग से Derived ऑब्जेक्ट की शुरुआत को इंगित करे।

मैनिपुलेटिंग पॉइंटर्स को आमतौर पर पॉइंटर अंकगणित के रूप में जाना जाता है। यहां कंपाइलर स्वचालित रूप से प्रकार के बारे में जागरूक होने की स्थिति पर सही समायोजन करेगा।

दुर्भाग्यवश संकलक void* से कनवर्ट करते समय उन्हें निष्पादित नहीं कर सकते हैं, यह इस प्रकार डेवलपर को यह सुनिश्चित करने के लिए है कि वह सही ढंग से इसे संभालता है। अंगूठे का सरल नियम निम्न है: T* -> void* -> T* दोनों पक्षों पर दिखाई देने वाले एक ही प्रकार के साथ।

इसलिए, आपको घोषित करके अपने कोड को सही (सही) करना चाहिए: IClassInterface* globalMember और आपके पास कोई पोर्टेबिलिटी समस्या नहीं होगी। आपके पास अभी भी रखरखाव समस्या होगी, लेकिन ओओ-कोड के साथ सी का उपयोग करने की समस्या है: सी किसी ऑब्जेक्ट उन्मुख सामग्री के बारे में पता नहीं है।

+0

मैं पूरी तरह समझ नहीं पा रहा हूं कि आप किस बारे में बात कर रहे हैं! :) लेकिन 1) मैं केवल निर्यातित सी फ़ंक्शन में शून्य * कर सकता हूं क्योंकि आईटी सी निर्यातित फ़ंक्शन है! ;) 2) यह पहली बार है जब मैं "सी ++ कंपाइलर ऑब्जेक्ट्स के लेआउट के आधार पर सही पॉइंटर अंकगणित कर सकता हूं" .. क्या ??:) –

+0

बीटीडब्ल्यू - मेरे डिजाइन के बारे में क्या गलत है? खैर - मुझे सी ++ पसंद है, लेकिन साझा पुस्तकालय केवल सी निर्यात किए गए कार्यों की अनुमति देता है। तब मुझे क्या करना चाहिए? लंगड़ा सी कोडर बनें और सी ++ ऑफर्स की सभी अच्छी चीजें छोड़ दें? :) –

+0

मुद्दा यह है कि आप मानते हैं कि सी ++ में एक ऑब्जेक्ट का एक अनोखा पता है, जिस तरह से आप इसका जिक्र कर रहे हैं। यह दुर्भाग्य से मामला नहीं है। मैं इसे चित्रित करने के लिए रीयलवर्ल्ड मानों के साथ एक छोटा सा उदाहरण जोड़ने के लिए अपनी पोस्ट संपादित करूंगा। –

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

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