gdb获取C++类成员函数地址
省流:info line xx(成员函数所在行号)即可查看函数入口地址
一. 获取类成员函数地址
在.cpp文件中声明并定义如下一个类
// memfunc_test.cpp
class Com_class {
public:
int mem1;
void func_c() {cout << "call func_common " << this->mem1 << endl;}
};
void func_c() 这个普通成员函数存放在何处,如何获取其地址?
依照直觉,是否如同成员变量一样,成员函数也是在创建(new)类实例对象时一同创建在呢?为了验证,首先创建一个类的实例对象:
int main() {
Com_class *cc = new Com_class{};
cc->mem1 = 1;
cc->func_c();
delete cc;
}
使用gdb查看对象的内存结构:
(gdb) p *cc
$1 = {
mem1 = 1
}
可以看到,对象只保存了成员变量,并没有保存成员函数,原因是为节省内存空间,所有类对象使用一个成员函数即可,而成员变量则独自保存在栈区或者堆区。用sizeof查看,也确实只有一个int类型的大小:
(gdb) p sizeof(*cc)
$7 = 4
那么,成员函数保存至何处,是在代码区(.text)吗?
如果是在代码区,那我们应该可以通过在gdb中使用如下指令查看函数入口地址:
(gdb) info line 8
Line 8 of "memfunc_test.cpp" starts at address 0x5555555552c2 \
<Com_class::func_c()> and ends at 0x5555555552d2 <Com_class::func_c()+16>.
第八行为文件中成员函数所在行,根据所示信息,似乎确实是成员函数地址。为了验证,我们从该地址直接调用函数,检查能否打印出正确结果:
!这个操作或许有风险
(gdb) print ( (void (*)()) 0x5555555552c2 )()
call func_common 0
$2 = void
确实是打印出了结果,但打印成员变量mem1的值却与预期的不同,我们先前已经将其赋值为1,但打印结果却是0。确是为何?
其实上面已经提到,所有对象公用一个成员函数,那么应当能够区分是哪个对象调用该函数。
因此,该函数并非是空参数的,而是需要传入对象的指针(this),我们修改为以下方式再试:
(gdb) print ( (void (*)(Com_class *)) 0x5555555552c2 )(cc)
call func_common 1
$3 = void
可以看到,这次打印的值符合预期了。可以确定这个地址确实是成员函数的地址,那么我们再查看此地址所在段:
(gdb) maintenance info sections
...
[14] 0x5555555550b0->0x555555555120 at 0x000010b0: .plt.sec ALLOC LOAD READONLY CODE HAS_CONTENTS
[15] 0x555555555120->0x555555555385 at 0x00001120: .text ALLOC LOAD READONLY CODE HAS_CONTENTS
[16] 0x555555555388->0x555555555395 at 0x00001388: .fini ALLOC LOAD READONLY CODE HAS_CONTENTS
...
很明显,该地址处在.text段的地址范围内,即成员函数存放在代码区。
虽然问题已经得到解决,不过以上实验也发现了一些有意思的行为,即函数明明需要传入参数,但当我们在gdb中并未传递参数的时候,函数也能顺利执行(虽然结果不对),按照以下方式也顺利运行了:
(gdb) print ( (void (*)()) 0x5555555552c2 )(cc)
call func_common 1
$9 = void
可以看到,我们并没有将地址强制类型转换为含参数的函数指针类型,直接传递参数也可正确运行。
关于函数如何传递参数的问题,我们需要从汇编的角度才可以解答,根据x86 calling convention,this指针的值是通过rdi寄存器传递的,为了验证,我们直接将rdi寄存器的值设置为对象的地址,并且不向函数传递任何参数:
(gdb) print ( (void (*)()) 0x5555555552c2 )()
call func_common 0
$1 = void
(gdb) print cc
$2 = (Com_class *) 0x55555556aeb0
(gdb) set var $rdi=0x55555556aeb0
(gdb) print ( (void (*)()) 0x5555555552c2 )()
call func_common 1
$3 = void
可以看到,即使不传递参数,直接调用函数也可以打印正确的值。有关函数调用的相关原理,将会专门在另一篇博文进行介绍。
————结束————
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?