C++类型混淆下错误static_cast后调用虚函数的vtable查找疑问
类型混淆场景下虚函数调用的底层逻辑
对vtable查找逻辑的常见误解
参考资料中“通过函数名索引vtable”是对实现逻辑的高层简化描述,编译完成后运行时的vtable结构完全不存储函数名信息,不存在按名称查找的环节。所有虚函数的查找索引都是编译阶段硬编码到二进制中的固定偏移量,和函数名没有任何运行时关联。
编译期对虚表的固定布局规则
编译器在处理包含虚函数的类继承体系时,会在编译期完成两个核心操作:
- 为每个带虚函数的类生成一张专属的vtable,本质是连续排列的函数指针数组,数组的下标(即槽位编号)完全由虚函数在类继承链中的声明顺序决定,一旦生成就不会改动。
- 每个类的实例对象在构造时,会把对象内存起始位置的虚表指针(vptr)指向自身类对应的vtable地址,这个vptr值也是运行时只读的。
对应到示例代码中的两个兄弟子类:
Execute类只定义了一个虚函数exec,它在Execute类的vtable中占据0号槽位(数组第0个元素)。Greeter类只定义了一个虚函数sayHi,它在Greeter类的vtable中同样占据0号槽位。
两个类的vtable里根本不存在其他条目,更不会记录条目对应的函数名。
错误转换场景下的实际执行流程
示例中的两行问题代码,实际运行时完全没有触发任何类型校验:
g = static_cast<Greeter*>(b2);static_cast对有继承关系的指针转换,不会在运行时做类型一致性检查,只是单纯把b2的指针值(指向Execute类实例的内存地址)硬赋值给g变量,直接抹除了原对象的真实类型标签。g->sayHi("/usr/bin/xcalc");
因为sayHi是Greeter类的虚函数,编译器在编译这行代码时,已经把调用逻辑硬编码为固定步骤:- 从
g指向的对象内存起始位置,读出vptr的值(也就是指向Execute类vtable的指针) - 按照编译期确定的固定偏移量(
sayHi在Greeter类中的槽位是0,对应偏移量为0),从vptr指向的数组中取出对应位置的函数指针——这个位置存的刚好是Execute::exec的入口地址 - 按照函数调用约定,把字符串参数
"/usr/bin/xcalc"传入寄存器/栈,直接调用取出的函数指针。
- 从
为什么参数会被传入exec执行
两个函数的签名完全一致:都是无返回值、接收一个const char*类型参数的普通成员函数,调用时的参数传递布局、栈帧结构完全匹配。被调用的exec函数拿到传入的字符串后,直接将其作为参数传给system执行,最终触发命令执行的效果。
这个错位的本质就是:虚表的槽位只认编译期分配的索引编号,不认函数的真实名称;当强制类型转换破坏了对象的真实类型标签时,编译期固定的索引查找逻辑就会取出完全意料之外的函数指针完成调用。
内容的提问来源于stack exchange,提问作者Jake
相关产品推荐
相关产品推荐

