আমি মাঝে মাঝে এমন প্রোগ্রামগুলি লক্ষ্য করি যা আমার কম্পিউটারে ত্রুটির সাথে ক্রাশ হয়: "খাঁটি ভার্চুয়াল ফাংশন কল"।
এই বিষয়গুলি কীভাবে সংকলন করতে পারে যখন কোনও বস্তু একটি বিমূর্ত শ্রেণীর তৈরি করা যায় না?
আমি মাঝে মাঝে এমন প্রোগ্রামগুলি লক্ষ্য করি যা আমার কম্পিউটারে ত্রুটির সাথে ক্রাশ হয়: "খাঁটি ভার্চুয়াল ফাংশন কল"।
এই বিষয়গুলি কীভাবে সংকলন করতে পারে যখন কোনও বস্তু একটি বিমূর্ত শ্রেণীর তৈরি করা যায় না?
উত্তর:
আপনি যদি কোনও নির্মাণকারী বা ডেস্ট্রাক্টরের কাছ থেকে ভার্চুয়াল ফাংশন কল করার চেষ্টা করেন তবে তাদের ফলাফল হতে পারে। যেহেতু আপনি কোনও কনস্ট্রাক্টর বা ডেস্ট্রাক্টরের কাছ থেকে ভার্চুয়াল ফাংশন কল করতে পারবেন না (উত্পন্ন ক্লাস অবজেক্টটি নির্মাণ করা হয়নি বা ইতিমধ্যে ধ্বংস হয়ে গেছে), এটি বেস ক্লাস সংস্করণকে কল করে, যা খাঁটি ভার্চুয়াল ফাংশনের ক্ষেত্রে না করে নেই।
( এখানে লাইভ ডেমো দেখুন )
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
}
doIt()
কনস্ট্রাক্টরের কলটি সহজেই ডেভ্যাচারুয়ালাইজড এবং Base::doIt()
স্ট্যাটিকভাবে প্রেরণ করা হয় , যা কেবলমাত্র একটি লিঙ্কারের ত্রুটির কারণ হয়ে থাকে। আমাদের সত্যিকার অর্থে যা দরকার তা হ'ল ডায়নামিক প্রেরণের সময় গতিশীল প্রকারটি বিমূর্ত বেস টাইপ।
Base::Base
একটি নন-ভার্চুয়ালকে f()
কল করুন যা ফলস্বরূপ (খাঁটি) ভার্চুয়াল doIt
পদ্ধতিতে কল করে ।
পাশাপাশি খাঁটি ভার্চুয়াল ফাংশন সহ কোনও বস্তুটির কনস্ট্রাক্টর বা ডেস্ট্রাক্টরের কাছ থেকে ভার্চুয়াল ফাংশন কল করার মানক ক্ষেত্রে আপনি যদি খালি ভার্চুয়াল ফাংশন কল পেতে পারেন (এমএসভিসিতে কমপক্ষে) আপনি যদি কোনও ভার্চুয়াল ফাংশন কল করেন তবে বস্তুটি ধ্বংস হয়ে যাওয়ার পরে if । স্পষ্টতই চেষ্টা করার চেষ্টা করা খুব খারাপ জিনিস তবে আপনি যদি ইন্টারফেস হিসাবে অ্যাবস্ট্রাক্ট ক্লাসের সাথে কাজ করছেন এবং আপনি যদি গোলমাল করেন তবে এটি এমন কিছু যা আপনি দেখতে পাচ্ছেন। আপনি সম্ভবত রেফারেন্সযুক্ত গণনা করা ইন্টারফেস ব্যবহার করছেন এবং আপনার যদি একটি রেফ গণনা বাগ আছে বা যদি আপনার কোনও মাল্টি-থ্রেড প্রোগ্রামে অবজেক্টের ব্যবহার / অবজেক্ট ধ্বংসের রেস শর্ত থাকে ... তবে এই ধরণের খাঁটি কলটি হ'ল এটি হ'ল ctor এবং dtor- এ ভার্চুয়াল কলগুলির 'সাধারণ সন্দেহভাজনদের' চেক হিসাবে কি ঘটছে তা সহজেই সহজে জানা যায়।
এমএসভিসির বিভিন্ন সংস্করণে, রানটাইম লাইব্রেরির খাঁটি কল হ্যান্ডলারটি প্রতিস্থাপন করে এই ধরণের সমস্যাগুলি ডিবাগ করতে সহায়তা করতে। আপনি এই স্বাক্ষর দিয়ে আপনার নিজস্ব ফাংশন সরবরাহ করে এটি করেন:
int __cdecl _purecall(void)
আপনি রানটাইম লাইব্রেরি লিঙ্ক করার আগে এবং এটি লিঙ্ক। এটি যখন খাঁটি ক্যাল সনাক্ত হয় তখন কী ঘটে তা আপনাকে নিয়ন্ত্রণ দেয়। একবার আপনার নিয়ন্ত্রণ হয়ে গেলে আপনি স্ট্যান্ডার্ড হ্যান্ডলারটির চেয়ে আরও কার্যকর কিছু করতে পারেন। আমার একটি হ্যান্ডলার রয়েছে যা খাঁটি কলটি কোথায় ঘটেছে তার স্ট্যাক ট্রেস সরবরাহ করতে পারে; আরও দেখুন এখানে: http://www.lenholgate.com/blog/2006/01/purecall.html আরও তথ্যের জন্য।
(দ্রষ্টব্য আপনি এমএসভিসির কয়েকটি সংস্করণে আপনার হ্যান্ডলারটি ইনস্টল করতে _set_purecall_handler () কেও কল করতে পারেন)।
_purecall()
মুছে ফেলা উদাহরণের পদ্ধতি কল করার জন্য সাধারণত অনুরোধটি ঘটবে না__declspec(novtable)
। এটির সাথে, অবজেক্টটি মোছার পরে ওভাররাইড ভার্চুয়াল পদ্ধতিতে কল করা সম্পূর্ণভাবে সম্ভব, যা আপনাকে অন্য কোনও আকারে কামড় না দেওয়া পর্যন্ত সমস্যার মুখোশ দিতে পারে। _purecall()
ফাঁদ তোমার বন্ধু!
সাধারণত যখন আপনি ঝোলা পয়েন্টারের মাধ্যমে ভার্চুয়াল ফাংশনটি কল করেন - সম্ভবত সম্ভবত ইতিমধ্যে ইভেন্টটি ধ্বংস হয়ে গেছে।
আরও "সৃজনশীল" কারণগুলিও থাকতে পারে: সম্ভবত আপনি যেখানে আপনার ভার্চুয়াল ফাংশনটি প্রয়োগ করেছিলেন সেখানে আপনার অবজেক্টের অংশটি স্লাইজ করতে পেরেছেন। তবে সাধারণত ঘটনাটি ইতিমধ্যে ধ্বংস হয়ে গেছে।
আমি দৃশ্যে দৌড়েছি যে খাঁটি ভার্চুয়াল ফাংশনগুলি ধ্বংস হওয়া বস্তুর কারণে ডাকা হয়, Len Holgate
ইতিমধ্যে খুব সুন্দর উত্তর রয়েছে , আমি উদাহরণ সহ কিছু রঙ যুক্ত করতে চাই:
ডেরাইভড ক্লাস ডেস্ট্রাক্টর ভিটিপিআর পয়েন্টগুলি বেস ক্লাস ভিটিবেলে রিসেট করে, যার বিশুদ্ধ ভার্চুয়াল ফাংশন রয়েছে, সুতরাং যখন আমরা ভার্চুয়াল ফাংশন বলি, এটি আসলে খাঁটি ভাইরালগুলিতে কল করে।
এটি একটি সুস্পষ্ট কোড বাগ বা মাল্টি-থ্রেডিং পরিবেশে জাতি শর্তের জটিল দৃশ্যের কারণে ঘটতে পারে।
এখানে একটি সাধারণ উদাহরণ রয়েছে (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
স্মৃতি যেমন অপারেটিং সিস্টেমে ফিরে এসেছি তেমন সহজভাবেই পেতে পারি এবং প্রোগ্রামটি কেবল এটি অ্যাক্সেস করতে পারে না। সুতরাং এই "খাঁটি ভার্চুয়াল ফাংশন কল" দৃশ্যটি সাধারণত ঘটে যখন বস্তুটি মেমোরি পুলে বরাদ্দ করা হয়, যখন কোনও বস্তু মুছে ফেলা হয়, অন্তর্নিহিত মেমরিটি আসলে ওএস দ্বারা পুনরুদ্ধার করা হয় না, এটি এখনও প্রক্রিয়াটির মাধ্যমে অ্যাক্সেসযোগ্য।
আমি অনুমান করতে পারি যে কোনও অভ্যন্তরীণ কারণে অ্যাবস্ট্রাক্ট শ্রেণির জন্য একটি ভিটিবিএল তৈরি করা হয়েছে (এটি কোনও ধরণের রান টাইম তথ্যের জন্য প্রয়োজন হতে পারে) এবং কিছু ভুল হয়ে যায় এবং একটি বাস্তব বস্তু এটি পায়। এটি একটি বাগ। একাকী এটি বলা উচিত যে ঘটতে পারে না এমন কিছু।
খাঁটি জল্পনা
সম্পাদনা: দেখে মনে হচ্ছে আমি প্রশ্নে ভুল করছি। OTOH আইআইআরসি কিছু ভাষা কনস্ট্রাক্টর ডিস্ট্রাক্টর থেকে ভিটিবিএল কলগুলি অনুমতি দেয় allow
আমি ভিএস 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 */
};
আপনি যদি বোরল্যান্ড / কোডগিয়ার / এম্বারকাডেরো / ইডেরা সি ++ বিল্ডার ব্যবহার করেন তবে আপনার স্রেফ বাস্তবায়ন করতে পারে
extern "C" void _RTLENTRY _pure_error_()
{
//_ErrorExit("Pure virtual function called");
throw Exception("Pure virtual function called");
}
ডিবাগিংয়ের সময় কোডটিতে একটি ব্রেকপয়েন্ট রাখুন এবং আইডিইতে কলস্ট্যাকটি দেখুন, অন্যথায় যদি আপনার কাছে উপযুক্ত সরঞ্জাম থাকে তবে আপনার ব্যতিক্রম হ্যান্ডলারের (বা সেই ফাংশন) কল স্ট্যাকটি লগ করুন। আমি ব্যক্তিগতভাবে এর জন্য ম্যাডেক্সেপ্ট ব্যবহার করি।
পুনশ্চ. আসল ফাংশন কলটি [সি ++ বিল্ডার] \ উত্স \ সিপিপিআরটিএল \ উত্স \ বিবিধ \ বিশুদ্ধর
এটি হওয়ার জন্য এখানে একটি স্নিগ্ধ উপায়। আজ আমার কাছে এটি মূলত ঘটেছিল।
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();
I had this essentially happen to me today
স্পষ্টতই সত্য নয়, কারণ কেবল ভুল: খাঁটি ভার্চুয়াল ফাংশনটি কেবল তখনই callFoo()
বলা হয় যখন কোনও কনস্ট্রাক্টরের (বা ডেস্ট্রাক্টর) ভিতরে ডাকা হয়, কারণ এই সময়ে অবজেক্টটি এখনও একটি পর্যায়ে (বা ইতিমধ্যে) থাকে। এখানে সিনট্যাক্স ত্রুটি ছাড়াই আপনার কোডের একটি চলমান সংস্করণ রয়েছে B b();
- প্রথম বন্ধনী এটি একটি ফাংশন ঘোষণা করে, আপনি কোনও বস্তু চান।