什么是多态
定义一个虚基类ISpeaker
class ISpeaker{
protected:
size_t b;
public:
ISpeaker( size_t _v )
: b(_v)
{}
virtual void speak() = 0;
};
有两个子类,都实现了虚函数speak():
class Dog : public ISpeaker {
public:
Dog()
: ISpeaker(0)
{}
//
virtual void speak() override {
printf("woof! %llu\n", b);
}
};
class Human : public ISpeaker {
private:
size_t c;
public:
Human()
: ISpeaker(1)
, c(2)
{}
virtual void speak() override {
printf("hello! %llu\n", c);
}
};
main方法中把Dog*和Human*类型的指针,退化(强转)成ISpeaker*指针,并调用speak()方法:
int main( int argc, char** _argv ) {
Human* pHuman = new Human();
Dog* pDog = new Dog();
//
ISpeaker* speaker1 = (ISpeaker*)pHuman;
ISpeaker* speaker2 = (ISpeaker*)pDog;
//
speaker2->speak();
speaker1->speak();
//
return 0;
}
输出:
woof! 0
hello! 2
同样类型的指针(都是ISpeaker*),其函数表现出不同的行为(输出不同),称为多态
编译器如何实现多态
以MSVC编译器为例
编译器处理虚类ISpeaker时,生成内存模型(结构体)__ispeaker,其成员有虚表指针SpeakerTable* vt
。
Dog继承ISpeaker,所以对应的虚表类__dog会包含__ispeaker中的成员;
Human继承ISpeaker,所以对应的虚表类__human会包含__ispeaker中的所有数据,此外还有自己的数据size_t c
而后,编译器为每个类生成唯一的虚表实例
__dogSpeakerTable和__humanSpeakerTable是虚表实例
虚表实例中的函数指针指向对应的函数
所以Dog和Human,对应的 虚表实例 中的函数指针指向的函数
不同。
这就是为什么speaker2->speak();
和speaker1->speak();
的输出不同。
在内存中,__dog和__humen的布局如下:
struct __dog {
const SpeakerTable* vt;
size_t b;
};
struct __human {
const SpeakerTable* vt;
size_t b;
size_t c;
};
因此,想访问b或者c成员时,是按虚表对象之后的偏移量来访问的。
调用__dog_speak和__human_speak时会传递对象__dog和__human作为参数:
int main( int _argc, char** _argv ) {
__dog* dog = createDog();
__human* human = createHuman();
//
__ispeaker* speaker1 = (__ispeaker*)dog;
__ispeaker* speaker2 = (__ispeaker*)human;
//
speaker1->vt->speak(speaker1);
speaker2->vt->speak(speaker2);
return 0;
}
访问b或c对象时,基于偏移量:
void __dog_speak( void* _ptr ) {
uint8_t* p = (uint8_t*)_ptr;
p+=sizeof(SpeakerTable*);
size_t b = *((size_t*)p);
printf("woof! %llu\n", b);
}
void __human_speak( void* _ptr ) {
uint8_t* p = (uint8_t*)_ptr;
p+=sizeof(SpeakerTable*);
p+=sizeof(size_t);
size_t b = *((size_t*)p);
printf("hello! %llu\n", b);
}
例如 uint8_t* p = (uint8_t*)_ptr;
拿到__dog对象的起始偏移,
p+=sizeof(SpeakerTable*);
是越过虚表对象,
size_t b = *((size_t*)p);
就找到b对象了。
反编译so时虚函数调用长什么样
参考:
http://showlinkroom.me/2017/08/21/C-%E9%80%86%E5%90%91%E5%88%86%E6%9E%90/
https://www.bilibili.com/video/BV15g4y1a7F3/?spm_id_from=333.337.search-card.all.click&vd_source=0fcfb2f2f346ba4bccf7f3ee3eb4ae69