"शुद्ध आभासी फ़ंक्शन कॉल" क्रैश कहां से आते हैं?


106

मैं कभी-कभी उन प्रोग्रामों को नोटिस करता हूं जो मेरे कंप्यूटर पर त्रुटि के साथ क्रैश होते हैं: "शुद्ध आभासी फ़ंक्शन कॉल"।

जब एक वस्तु एक अमूर्त वर्ग से नहीं बनाई जा सकती है तो ये कार्यक्रम कैसे संकलित करते हैं?

जवाबों:


107

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

(लाइव डेमो देखें यहाँ )

class Base
{
public:
    Base() { doIt(); }  // DON'T DO THIS
    virtual void doIt() = 0;
};

void Base::doIt()
{
    std::cout<<"Is it fine to call pure virtual function from constructor?";
}

class Derived : public Base
{
    void doIt() {}
};

int main(void)
{
    Derived d;  // This will cause "pure virtual function call" error
}

3
किसी भी कारण से संकलक इसे सामान्य रूप से नहीं पकड़ सकता है?
थॉमस

21
सामान्य स्थिति में इसे नहीं पकड़ सकते क्योंकि ctor से प्रवाह कहीं भी जा सकता है और कहीं भी शुद्ध आभासी कार्य कह सकता है। यह
हॉल्टिंग

9
उत्तर थोड़ा गलत है: एक शुद्ध आभासी फ़ंक्शन को अभी भी परिभाषित किया जा सकता है, विवरण के लिए विकिपीडिया देखें। सही
चेतावनियाँ

5
मुझे लगता है कि यह उदाहरण बहुत सरल है: doIt()कंस्ट्रक्टर में कॉल आसानी से भक्तिपूर्ण है और सांख्यिकीय Base::doIt()रूप से प्रेषित की जाती है , जो सिर्फ एक लिंकर त्रुटि का कारण बनता है। हमें वास्तव में एक ऐसी स्थिति की आवश्यकता है जिसमें गतिशील प्रेषण के दौरान गतिशील प्रकार अमूर्त आधार प्रकार है।
केरेक एसबी

2
इसे MSVC के साथ ट्रिगर किया जा सकता है यदि आप अप्रत्यक्ष के अतिरिक्त स्तर को जोड़ते हैं: Base::Baseएक गैर-आभासी f()कॉल करें, जो बदले में (शुद्ध) आभासी doItविधि को कॉल करता है ।
फ्रैरिच राबे

64

साथ ही वर्चुअल वर्चुअल फ़ंक्शन को कंस्ट्रक्टर या ऑब्जेक्ट के डिस्ट्रक्टर से शुद्ध वर्चुअल फ़ंक्शंस में कॉल करने के मानक मामले में आप शुद्ध वर्चुअल फ़ंक्शन कॉल (MSVC पर कम से कम) प्राप्त कर सकते हैं यदि आप ऑब्जेक्ट को नष्ट करने के बाद वर्चुअल फ़ंक्शन को कॉल करते हैं। । जाहिर तौर पर यह कोशिश करने और करने के लिए एक बहुत बुरी बात है, लेकिन अगर आप अमूर्त वर्गों के साथ इंटरफेस के रूप में काम कर रहे हैं और आप गड़बड़ करते हैं तो यह कुछ ऐसा है जो आप देख सकते हैं। यह संभवतः अधिक संभावना है यदि आप संदर्भित काउंटेड इंटरफेस का उपयोग कर रहे हैं और आपके पास एक रेफ काउंट बग है या यदि आपके पास एक बहु-थ्रेडेड प्रोग्राम में ऑब्जेक्ट उपयोग / ऑब्जेक्ट विनाश रेस की स्थिति है ... तो इन प्रकार के प्योरकॉल की बात यह है कि यह अक्सर कम करने के लिए आसान है क्या ctor और dtor में आभासी कॉल के 'सामान्य संदिग्धों' के लिए एक चेक के रूप में चल रहा है साफ आ जाएगा।

MSVC के विभिन्न संस्करणों में, आप कर सकते हैं इन प्रकार की समस्याओं को डीबग करने में मदद करने के लिए, रनटाइम लाइब्रेरी के प्योरकॉल हैंडलर को बदलें। आप इस हस्ताक्षर के साथ अपना कार्य प्रदान करके करते हैं:

int __cdecl _purecall(void)

और रनटाइम लाइब्रेरी को लिंक करने से पहले इसे लिंक करना। यह आपको नियंत्रित करता है कि जब एक शुद्ध कॉल का पता चलता है तो क्या होता है। एक बार नियंत्रण करने के बाद आप मानक हैंडलर की तुलना में अधिक उपयोगी कुछ कर सकते हैं। मेरे पास एक हैंडलर है जो स्टैक का पता लगा सकता है कि प्योरसेल कहां हुआ; यहाँ देखें: अधिक जानकारी के लिए http://www.lenholgate.com/blog/2006/01/purecall.html

(ध्यान दें कि आप MSVC के कुछ संस्करणों में अपने हैंडलर को स्थापित करने के लिए _set_purecall_handler () भी कॉल कर सकते हैं)।


1
हटाए गए उदाहरण पर _purecall () मंगलाचरण प्राप्त करने के बारे में सूचक के लिए धन्यवाद; मुझे उस बारे में पता नहीं था, लेकिन बस थोड़ा परीक्षण कोड के साथ खुद को साबित कर दिया। WinDbg में एक पोस्टमॉर्टम डंप को देखते हुए मुझे लगा कि मैं एक दौड़ से निपट रहा था जहां एक और धागा एक व्युत्पन्न वस्तु का उपयोग करने से पहले पूरी तरह से निर्माण करने की कोशिश कर रहा था, लेकिन इस मुद्दे पर एक नई रोशनी चमकती है, और सबूत को बेहतर ढंग से फिट करने के लिए लगता है।
डेव रस्के

1
एक और चीज़ जो मैं _purecall()जोड़ूंगा : एक आम तौर पर डिलीट किए गए इंस्टेंस के एक तरीके को कॉल करने पर होने वाला इनवोकेशन तब नहीं होगा जब बेस क्लास को __declspec(novtable)ऑप्टिमाइज़ेशन (Microsoft विशिष्ट) के साथ घोषित किया गया हो । इसके साथ, ऑब्जेक्ट को हटाए जाने के बाद एक ओवरराइड वर्चुअल विधि को कॉल करना पूरी तरह से संभव है, जो समस्या को तब तक मास्क कर सकता है जब तक कि यह आपको किसी अन्य रूप में काटता नहीं है। _purecall()जाल अपने दोस्त है!
डेव रस्के

डेव को जानने के लिए यह उपयोगी है, मैंने हाल ही में कुछ स्थितियों को देखा है, जहां मुझे लगता है कि जब मुझे होना चाहिए तो मुझे शुद्ध नहीं मिल रहा था। शायद मैं उस अनुकूलन की बेईमानी कर रहा था।
लेन होल्गेट

1
@ लेनहॉलगेट: अत्यधिक मूल्यवान उत्तर। यह वास्तव में हमारी समस्या का मामला था (नस्ल स्थितियों के कारण गलत रिफ-काउंट)। हमें सही दिशा में इंगित करने के लिए बहुत-बहुत धन्यवाद (हम इसके बजाय v- टेबल भ्रष्टाचार पर संदेह कर रहे थे और अपराधी कोड खोजने की कोशिश में पागल हो रहे थे)
BlueStrat

7

आमतौर पर जब आप किसी झूलने वाले पॉइंटर के जरिए वर्चुअल फंक्शन कहते हैं - तो संभवत: उदाहरण पहले ही नष्ट हो चुका है।

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


4

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

  1. एक व्युत्पन्न वस्तु बनाई जाती है, और पॉइंटर (बेस क्लास के रूप में) कहीं बच जाता है
  2. व्युत्पन्न वस्तु को हटा दिया जाता है, लेकिन किसी तरह सूचक को अभी भी संदर्भित किया जाता है
  3. डिलीट किए गए ऑब्जेक्ट को इंगित करने वाला पॉइंटर जिसे कहा जाता है

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

यह स्पष्ट कोड बग, या बहु-थ्रेडिंग वातावरणों में दौड़ की स्थिति का एक जटिल परिदृश्य के कारण हो सकता है।

यहाँ एक सरल उदाहरण है (अनुकूलन के साथ जी ++ संकलन बंद हो गया - एक सरल कार्यक्रम आसानी से दूर अनुकूलित किया जा सकता है):

 #include <iostream>
 using namespace std;

 char pool[256];

 struct Base
 {
     virtual void foo() = 0;
     virtual ~Base(){};
 };

 struct Derived: public Base
 {
     virtual void foo() override { cout <<"Derived::foo()" << endl;}
 };

 int main()
 {
     auto* pd = new (pool) Derived();
     Base* pb = pd;
     pd->~Derived();
     pb->foo();
 }

और स्टैक ट्रेस की तरह दिखता है:

#0  0x00007ffff7499428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
#1  0x00007ffff749b02a in __GI_abort () at abort.c:89
#2  0x00007ffff7ad78f7 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3  0x00007ffff7adda46 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4  0x00007ffff7adda81 in std::terminate() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#5  0x00007ffff7ade84f in __cxa_pure_virtual () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#6  0x0000000000400f82 in main () at purev.C:22

हाइलाइट:

यदि ऑब्जेक्ट पूरी तरह से हटा दिया गया है, तो अर्थ डिस्ट्रक्टर कहा जाता है, और मेमोरो पुनः प्राप्त हो जाता है, हम बस प्राप्त कर सकते हैं Segmentation faultजैसे कि मेमोरी ऑपरेटिंग सिस्टम में वापस आ गई है, और प्रोग्राम बस इसे एक्सेस नहीं कर सकता है। तो यह "शुद्ध आभासी फ़ंक्शन कॉल" परिदृश्य आमतौर पर तब होता है जब ऑब्जेक्ट को मेमोरी पूल पर आवंटित किया जाता है, जबकि एक ऑब्जेक्ट हटा दिया जाता है, अंतर्निहित मेमोरी वास्तव में ओएस द्वारा पुनर्प्राप्त नहीं की जाती है, यह अभी भी प्रक्रिया द्वारा सुलभ है।


0

मुझे लगता है कि कुछ आंतरिक कारण के लिए अमूर्त वर्ग के लिए एक vtbl बनाया गया है (यह किसी प्रकार की रन टाइम जानकारी के लिए आवश्यक हो सकता है) और कुछ गलत हो जाता है और एक वास्तविक वस्तु मिल जाती है। यह एक बग है। अकेले ही कहना चाहिए कि ऐसा कुछ नहीं हो सकता है।

शुद्ध सट्टा

संपादित करें: ऐसा लगता है कि मैं प्रश्न में मामले में गलत हूं। OTOH IIRC कुछ भाषाएं कंस्ट्रक्टर डिस्ट्रक्टर से vtbl कॉल की अनुमति देती हैं।


यह संकलक में एक बग नहीं है, अगर यही आपका मतलब है।
थॉमस

आपका संदेह सही है - C # और Java इसकी अनुमति देते हैं। उन भाषाओं में, निर्माणाधीन bohjects में उनका अंतिम प्रकार होता है। C ++ में, ऑब्जेक्ट निर्माण के दौरान प्रकार बदलते हैं और इसीलिए जब आप किसी अमूर्त प्रकार के साथ ऑब्जेक्ट रख सकते हैं।
MSalters

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

0

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

template <typename T>
class Foo {
public:
  Foo<T>() {};
  ~Foo<T>() {};

public:
  void SomeMethod1() { this->~Foo(); }; /* ERROR */
};

इसलिए मैंने निजी विधि को अलग करने के लिए ~ फू () के अंदर क्या स्थानांतरित किया, फिर इसने एक आकर्षण की तरह काम किया।

template <typename T>
class Foo {
public:
  Foo<T>() {};
  ~Foo<T>() {};

public:
  void _MethodThatDestructs() {};
  void SomeMethod1() { this->_MethodThatDestructs(); }; /* OK */
};

0

यदि आप Borland / CodeGear / Embarcadero / Idera C ++ बिल्डर का उपयोग करते हैं, तो आप बस लागू कर सकते हैं

extern "C" void _RTLENTRY _pure_error_()
{
    //_ErrorExit("Pure virtual function called");
    throw Exception("Pure virtual function called");
}

डिबगिंग कोड में एक ब्रेकपॉइंट रखें और आईडीई में कॉलस्टैक देखें, अन्यथा कॉल अपवाद को अपने अपवाद हैंडलर (या फ़ंक्शन) में लॉग इन करें यदि आपके पास उसके लिए उपयुक्त उपकरण हैं। मैं व्यक्तिगत रूप से उसके लिए MadExcept का उपयोग करता हूं।

पुनश्च। मूल फ़ंक्शन कॉल [C ++ बिल्डर] \ source \ cpprtl \ Source \ misc \ pureerr.cpp में है


-2

यहाँ यह होने के लिए एक डरपोक तरीका है। आज मेरे साथ ऐसा अनिवार्य रूप से हुआ था।

class A
{
  A *pThis;
  public:
  A()
   : pThis(this)
  {
  }

  void callFoo()
  {
    pThis->foo(); // call through the pThis ptr which was initialized in the constructor
  }

  virtual void foo() = 0;
};

class B : public A
{
public:
  virtual void foo()
  {
  }
};

B b();
b.callFoo();

1
कम से कम यह मेरे vc2008 पर पुन: पेश नहीं किया जा सकता है, vptr A के वाइबल की ओर इशारा करता है जब पहली बार A के कंस्ट्रक्टर में इनिशियलाइज़ किया जाता है, लेकिन तब जब B को पूरी तरह से इनिशियलाइज़ किया जाता है, तो Vptr को B के वॉयटेबल में बदल दिया जाता है, जो कि ठीक है
बैयान हुआंग

coudnt इसे या तो vs2010 / 12
makc

I had this essentially happen to me todayस्पष्ट रूप से सच नहीं है, क्योंकि केवल गलत है: एक शुद्ध आभासी फ़ंक्शन को केवल तभी कहा जाता है जब callFoo()एक निर्माता (या विध्वंसक) के भीतर बुलाया जाता है, क्योंकि इस समय ए चरण में वस्तु अभी भी (या पहले से ही) है। यहां सिंटैक्स त्रुटि के बिना आपके कोड का एक रनिंग संस्करण है B b();- कोष्ठक इसे एक फ़ंक्शन घोषणा बनाते हैं, आप एक ऑब्जेक्ट चाहते हैं।
वुल्फ
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.