"খাঁটি ভার্চুয়াল ফাংশন কল" ক্রাশগুলি কোথা থেকে আসে?


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
সাধারণ ক্ষেত্রে এটি ধরতে পারে না কারণ কর্টর থেকে প্রবাহ যে কোনও জায়গায় যেতে পারে এবং যে কোনও জায়গায় খাঁটি ভার্চুয়াল ফাংশনটি কল করতে পারে। এটি
হোলটিং

9
উত্তর কিছুটা ভুল: খাঁটি ভার্চুয়াল ফাংশনটি এখনও সংজ্ঞায়িত করা যেতে পারে, বিশদগুলির জন্য উইকিপিডিয়া দেখুন। সঠিক
বাক্যাংশ

5
আমি মনে করি যে এই উদাহরণটি খুব সরল: doIt()কনস্ট্রাক্টরের কলটি সহজেই ডেভ্যাচারুয়ালাইজড এবং Base::doIt()স্ট্যাটিকভাবে প্রেরণ করা হয় , যা কেবলমাত্র একটি লিঙ্কারের ত্রুটির কারণ হয়ে থাকে। আমাদের সত্যিকার অর্থে যা দরকার তা হ'ল ডায়নামিক প্রেরণের সময় গতিশীল প্রকারটি বিমূর্ত বেস টাইপ।
কেরেক এসবি

2
যদি আপনি অতিরিক্ত স্তরের ইন্ডিরিশন যোগ করেন তবে এটি এমএসভিসির সাথে ট্রিগার হতে পারে: Base::Baseএকটি নন-ভার্চুয়ালকে f()কল করুন যা ফলস্বরূপ (খাঁটি) ভার্চুয়াল doItপদ্ধতিতে কল করে ।
ফ্রিরিচ রাবাবে 14'20

64

পাশাপাশি খাঁটি ভার্চুয়াল ফাংশন সহ কোনও বস্তুটির কনস্ট্রাক্টর বা ডেস্ট্রাক্টরের কাছ থেকে ভার্চুয়াল ফাংশন কল করার মানক ক্ষেত্রে আপনি যদি খালি ভার্চুয়াল ফাংশন কল পেতে পারেন (এমএসভিসিতে কমপক্ষে) আপনি যদি কোনও ভার্চুয়াল ফাংশন কল করেন তবে বস্তুটি ধ্বংস হয়ে যাওয়ার পরে if । স্পষ্টতই চেষ্টা করার চেষ্টা করা খুব খারাপ জিনিস তবে আপনি যদি ইন্টারফেস হিসাবে অ্যাবস্ট্রাক্ট ক্লাসের সাথে কাজ করছেন এবং আপনি যদি গোলমাল করেন তবে এটি এমন কিছু যা আপনি দেখতে পাচ্ছেন। আপনি সম্ভবত রেফারেন্সযুক্ত গণনা করা ইন্টারফেস ব্যবহার করছেন এবং আপনার যদি একটি রেফ গণনা বাগ আছে বা যদি আপনার কোনও মাল্টি-থ্রেড প্রোগ্রামে অবজেক্টের ব্যবহার / অবজেক্ট ধ্বংসের রেস শর্ত থাকে ... তবে এই ধরণের খাঁটি কলটি হ'ল এটি হ'ল ctor এবং dtor- এ ভার্চুয়াল কলগুলির 'সাধারণ সন্দেহভাজনদের' চেক হিসাবে কি ঘটছে তা সহজেই সহজে জানা যায়।

এমএসভিসির বিভিন্ন সংস্করণে, রানটাইম লাইব্রেরির খাঁটি কল হ্যান্ডলারটি প্রতিস্থাপন করে এই ধরণের সমস্যাগুলি ডিবাগ করতে সহায়তা করতে। আপনি এই স্বাক্ষর দিয়ে আপনার নিজস্ব ফাংশন সরবরাহ করে এটি করেন:

int __cdecl _purecall(void)

আপনি রানটাইম লাইব্রেরি লিঙ্ক করার আগে এবং এটি লিঙ্ক। এটি যখন খাঁটি ক্যাল সনাক্ত হয় তখন কী ঘটে তা আপনাকে নিয়ন্ত্রণ দেয়। একবার আপনার নিয়ন্ত্রণ হয়ে গেলে আপনি স্ট্যান্ডার্ড হ্যান্ডলারটির চেয়ে আরও কার্যকর কিছু করতে পারেন। আমার একটি হ্যান্ডলার রয়েছে যা খাঁটি কলটি কোথায় ঘটেছে তার স্ট্যাক ট্রেস সরবরাহ করতে পারে; আরও দেখুন এখানে: http://www.lenholgate.com/blog/2006/01/purecall.html আরও তথ্যের জন্য।

(দ্রষ্টব্য আপনি এমএসভিসির কয়েকটি সংস্করণে আপনার হ্যান্ডলারটি ইনস্টল করতে _set_purecall_handler () কেও কল করতে পারেন)।


1
মুছে ফেলা দৃষ্টান্তে একটি_পুরিকাল () প্রার্থনা পাওয়ার বিষয়ে নির্দেশকের জন্য ধন্যবাদ; আমি এটি সম্পর্কে অবগত ছিলাম না, তবে কেবল এটি একটি সামান্য পরীক্ষার কোড দিয়ে প্রমাণ করেছি। উইনডিবিজি-তে একটি পোস্টমর্টেম ডাম্পের দিকে তাকিয়ে আমি ভেবেছিলাম যে আমি এমন একটি দৌড়ের সাথে কাজ করছি যেখানে অন্য থ্রেডটি সম্পূর্ণরূপে নির্মাণের আগে কোনও উদ্ভূত বস্তুটি ব্যবহার করার চেষ্টা করছিল, তবে এটি ইস্যুতে একটি নতুন আলো জ্বলজ্বলে করে এবং প্রমাণকে আরও ফিট করে বলে মনে হয়।
ডেভ রুস্কে

1
অন্য একটি জিনিস আমি যুক্ত করব: বেস ক্লাসটি অপটিমাইজেশন (মাইক্রোসফ্ট নির্দিষ্ট) দিয়ে ঘোষণা করা থাকলে _purecall()মুছে ফেলা উদাহরণের পদ্ধতি কল করার জন্য সাধারণত অনুরোধটি ঘটবে না__declspec(novtable) । এটির সাথে, অবজেক্টটি মোছার পরে ওভাররাইড ভার্চুয়াল পদ্ধতিতে কল করা সম্পূর্ণভাবে সম্ভব, যা আপনাকে অন্য কোনও আকারে কামড় না দেওয়া পর্যন্ত সমস্যার মুখোশ দিতে পারে। _purecall()ফাঁদ তোমার বন্ধু!
ডেভ রুস্কে

ডেভকে জানার জন্য এটি দরকারী, আমি সম্প্রতি এমন কয়েকটি পরিস্থিতি দেখেছি যেখানে আমার মনে হওয়া উচিত যখন আমি খাঁটি কলগুলি পাচ্ছিলাম না। সম্ভবত আমি সেই অপ্টিমাইজেশানটিকে ঘৃণা করছিলাম।
লেন হলগেট

1
@ লেনহোলগেট: অত্যন্ত মূল্যবান উত্তর। এটি ছিল আমাদের সমস্যা ক্ষেত্রে (জাতিগত অবস্থার কারণে ভুল রেফ-কাউন্ট)। আমাদের সঠিক দিকে নির্দেশ করার জন্য আপনাকে অনেক ধন্যবাদ (আমরা এর পরিবর্তে ভি-টেবিল দুর্নীতির বিষয়ে সন্দেহ করছিলাম এবং অপরাধীর কোড সন্ধান করার জন্য পাগল হয়ে
যাচ্ছিলাম

7

সাধারণত যখন আপনি ঝোলা পয়েন্টারের মাধ্যমে ভার্চুয়াল ফাংশনটি কল করেন - সম্ভবত সম্ভবত ইতিমধ্যে ইভেন্টটি ধ্বংস হয়ে গেছে।

আরও "সৃজনশীল" কারণগুলিও থাকতে পারে: সম্ভবত আপনি যেখানে আপনার ভার্চুয়াল ফাংশনটি প্রয়োগ করেছিলেন সেখানে আপনার অবজেক্টের অংশটি স্লাইজ করতে পেরেছেন। তবে সাধারণত ঘটনাটি ইতিমধ্যে ধ্বংস হয়ে গেছে।


4

আমি দৃশ্যে দৌড়েছি যে খাঁটি ভার্চুয়াল ফাংশনগুলি ধ্বংস হওয়া বস্তুর কারণে ডাকা হয়, Len Holgateইতিমধ্যে খুব সুন্দর উত্তর রয়েছে , আমি উদাহরণ সহ কিছু রঙ যুক্ত করতে চাই:

  1. একটি উত্পন্ন বস্তু তৈরি হয় এবং পয়েন্টারটি (বেস শ্রেণি হিসাবে) কোথাও সংরক্ষণ করা হয়
  2. উত্পন্ন বস্তুটি মোছা হয়েছে, তবে কোনওভাবে পয়েন্টারটি এখনও রেফারেন্স করা আছে
  3. মুছে ফেলা ডারাইভড অবজেক্টকে নির্দেশকারী পয়েন্টারটি কল হয়

ডেরাইভড ক্লাস ডেস্ট্রাক্টর ভিটিপিআর পয়েন্টগুলি বেস ক্লাস ভিটিবেলে রিসেট করে, যার বিশুদ্ধ ভার্চুয়াল ফাংশন রয়েছে, সুতরাং যখন আমরা ভার্চুয়াল ফাংশন বলি, এটি আসলে খাঁটি ভাইরালগুলিতে কল করে।

এটি একটি সুস্পষ্ট কোড বাগ বা মাল্টি-থ্রেডিং পরিবেশে জাতি শর্তের জটিল দৃশ্যের কারণে ঘটতে পারে।

এখানে একটি সাধারণ উদাহরণ রয়েছে (g ++ অপটিমাইজেশন সহ সংকলন বন্ধ রয়েছে - একটি সাধারণ প্রোগ্রাম সহজেই অপ্টিমাইজ করা যেতে পারে):

 #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

আমি অনুমান করতে পারি যে কোনও অভ্যন্তরীণ কারণে অ্যাবস্ট্রাক্ট শ্রেণির জন্য একটি ভিটিবিএল তৈরি করা হয়েছে (এটি কোনও ধরণের রান টাইম তথ্যের জন্য প্রয়োজন হতে পারে) এবং কিছু ভুল হয়ে যায় এবং একটি বাস্তব বস্তু এটি পায়। এটি একটি বাগ। একাকী এটি বলা উচিত যে ঘটতে পারে না এমন কিছু।

খাঁটি জল্পনা

সম্পাদনা: দেখে মনে হচ্ছে আমি প্রশ্নে ভুল করছি। OTOH আইআইআরসি কিছু ভাষা কনস্ট্রাক্টর ডিস্ট্রাক্টর থেকে ভিটিবিএল কলগুলি অনুমতি দেয় allow


এটি যদি আপনি বোঝাতে চান তবে এটি সংকলকটিতে কোনও বাগ নয়।
টমাস

আপনার সন্দেহ ঠিক - সি # এবং জাভা এটির অনুমতি দেয়। এই ভাষাগুলিতে, নির্মাণাধীন বোহেক্টগুলির চূড়ান্ত প্রকার রয়েছে। সি ++-তে, নির্মাণের সময় অবজেক্টগুলি ধরণ বদলে দেয় এবং সে কারণেই এবং কখন আপনাকে বিমূর্ত প্রকারের সাহায্যে বস্তু থাকতে পারে।
এমসাল্টারস

সমস্ত বিমূর্ত শ্রেণি, এবং এগুলি থেকে প্রাপ্ত সত্যিকারের অবজেক্টগুলির জন্য একটি ভিটিবিএল (ভার্চুয়াল ফাংশন টেবিল) প্রয়োজন, যার উপরে ভার্চুয়াল ফাংশনগুলি কল করা উচিত listing সি ++ তে কোনও বস্তু ভার্চুয়াল ফাংশন সারণী সহ তার নিজস্ব সদস্য তৈরির জন্য দায়বদ্ধ। কনস্ট্রাক্টরগুলি বেস ক্লাস থেকে ডেরাইভেড ডেকে আনা হয় এবং ডিস্ট্রাক্টরদের ড্রেইড থেকে ডান বেস ক্লাসে ডেকে আনা হয়, সুতরাং অ্যাবস্ট্রাক্ট বেস ক্লাসে ভার্চুয়াল ফাংশন টেবিলটি এখনও পাওয়া যায় না।
fuzzyTew

0

আমি ভিএস 2010 ব্যবহার করি এবং যখনই আমি সর্বজনীন পদ্ধতি থেকে সরাসরি ডেস্ট্রাক্টরকে কল করার চেষ্টা করি, রানটাইমের সময় আমি "খাঁটি ভার্চুয়াল ফাংশন কল" ত্রুটি পাই।

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

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

তাই আমি ব্যক্তিগত পদ্ধতি পৃথক করতে inside ফু () এর ভিতরে থাকা স্থানান্তরিত করেছিলাম, তারপরে এটি একটি কবজির মতো কাজ করেছিল।

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

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

0

আপনি যদি বোরল্যান্ড / কোডগিয়ার / এম্বারকাডেরো / ইডেরা সি ++ বিল্ডার ব্যবহার করেন তবে আপনার স্রেফ বাস্তবায়ন করতে পারে

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

ডিবাগিংয়ের সময় কোডটিতে একটি ব্রেকপয়েন্ট রাখুন এবং আইডিইতে কলস্ট্যাকটি দেখুন, অন্যথায় যদি আপনার কাছে উপযুক্ত সরঞ্জাম থাকে তবে আপনার ব্যতিক্রম হ্যান্ডলারের (বা সেই ফাংশন) কল স্ট্যাকটি লগ করুন। আমি ব্যক্তিগতভাবে এর জন্য ম্যাডেক্সেপ্ট ব্যবহার করি।

পুনশ্চ. আসল ফাংশন কলটি [সি ++ বিল্ডার] \ উত্স \ সিপিপিআরটিএল \ উত্স \ বিবিধ \ বিশুদ্ধর


-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 এ পুনরুত্পাদন করা যাবে না, ভিপিটিআর এর কন্ট্রাক্টরে প্রথম সূচনা করার সময় এ এর ​​ভিটিবেলের দিকে ইঙ্গিত দেয়, তবে তারপরে বি সম্পূর্ণভাবে আরম্ভ করা হয়, তখন ভিপিটিআরটি বি এর ভ্যাটেবলের দিকে নির্দেশিত হয়, যা ঠিক আছে
বৈয়ান হুয়াং

কাউডেন্ট এটি vs2010 / 12 দিয়ে পুনরুত্পাদন করেছে
ম্যাক

I had this essentially happen to me todayস্পষ্টতই সত্য নয়, কারণ কেবল ভুল: খাঁটি ভার্চুয়াল ফাংশনটি কেবল তখনই callFoo()বলা হয় যখন কোনও কনস্ট্রাক্টরের (বা ডেস্ট্রাক্টর) ভিতরে ডাকা হয়, কারণ এই সময়ে অবজেক্টটি এখনও একটি পর্যায়ে (বা ইতিমধ্যে) থাকে। এখানে সিনট্যাক্স ত্রুটি ছাড়াই আপনার কোডের একটি চলমান সংস্করণ রয়েছে B b();- প্রথম বন্ধনী এটি একটি ফাংশন ঘোষণা করে, আপনি কোনও বস্তু চান।
ওল্ফ
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.