【一分钟学C++】函数地址探究

在这里插入图片描述

竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生~
公众号: C++学习与探索  |  个人主页: rainInSunny  |  个人专栏: Learn OpenGL In Qt

写在前面

  之前一直以为通过函数指针能够直接获取到函数地址(这里的函数地址指的是汇编层面call指令跳转的地址),最近实验才发现并不是这样的,并且获取函数地址过程中包含着一些坑。话不多说,先看测试代码。

测试代码

#include <iostream>
#include <stdio.h>

int func_0(int a, int b)
{
    return a + b;
}

class A
{
public:
    A() {}
    ~A() {}

    void func_1() { std::cout << "func_1 in A!" << std::endl; }
    static void func_2() { std::cout << "func_2 in A!" << std::endl; }
    virtual void func_3() { std::cout << "func_3 in A!" << std::endl; }
    virtual void func_4() { std::cout << "func_4 in A!" << std::endl; }
};

template<typename addressType, typename funcType>
addressType union_cast(funcType func_ptr)        // 获取类内成员函数的函数地址
{
    union
    {
        funcType f;
        addressType d;
    }u;
    u.f = func_ptr;
    return u.d;
}

#define asm_cast(var, addr)        \
{                                \
    __asm                        \
    {                            \
        mov var, offset addr    \
    }                            \
}

  func_0是一个普通函数,用于测试普通函数的函数地址。func_1是类中的普通成员函数,func_2是类中的静态成员函数,func_3func_4是类中的虚函数,分别用于测试类中函数的函数地址。union_cast是一种获取类中成员函数地址的方法,asm_cast是通过汇编获取类中成员函数地址的方法(win32)。

int main()
{
    A *pA = new A;
    pA->func_3();
    typedef int(*func_0_pointer)(int a, int b);
    func_0_pointer pFunc_0 = func_0;

    std::cout << std::endl;
    std::cout << "print normal function" << std::endl;
    std::cout << "func_0: " << pFunc_0 << std::endl;
    std::cout << std::endl;

    typedef void(A::*func_1_pointer)();
    typedef void(*func_2_pointer)();
    typedef void(A::*func_3_pointer)();
    typedef void(A::*func_4_pointer)();
    func_1_pointer pFunc_1 = &A::func_1;
    func_2_pointer pFunc_2 = &A::func_2;
    func_3_pointer pFunc_3 = &A::func_3;
    func_4_pointer pFunc_4 = &A::func_4;
    
    std::cout << "print func_pointer" << std::endl;
    std::cout << "A::func_1: " << pFunc_1 << std::endl;
    std::cout << "A::func_2: " << pFunc_2 << std::endl;
    std::cout << "A::func_3: " << pFunc_3 << std::endl;
    std::cout << "A::func_4: " << pFunc_3 << std::endl;
    std::cout << std::endl;
    
    void *ptr_1 = union_cast<void *>(&A::func_1);
    void *ptr_2 = union_cast<void *>(&A::func_2);
    void *ptr_3 = union_cast<void *>(&A::func_3);
    void *ptr_4 = union_cast<void *>(&A::func_4);
    
    std::cout << "print union_cast" << std::endl;
    std::cout << "A::func_1: " << ptr_1 << std::endl;
    std::cout << "A::func_2: " << ptr_2 << std::endl;
    std::cout << "A::func_3: " << ptr_3 << std::endl;
    std::cout << "A::func_4: " << ptr_4 << std::endl;
    std::cout << std::endl;
    
    void *ptr_5 = nullptr;
    void *ptr_6 = nullptr;
    void *ptr_7 = nullptr;
    void *ptr_8 = nullptr;
    asm_cast(ptr_5, A::func_1);
    asm_cast(ptr_6, A::func_2);
    asm_cast(ptr_7, A::func_3);
    asm_cast(ptr_8, A::func_4);

    std::cout << "print asm_cast" << std::endl;
    std::cout << "A::func_1: " << ptr_5 << std::endl;
    std::cout << "A::func_2: " << ptr_6 << std::endl;
    std::cout << "A::func_3: " << ptr_7 << std::endl;
    std::cout << "A::func_4: " << ptr_8 << std::endl;
    std::cout << std::endl;

    std::cout << "print printf" << std::endl;
    printf("A::func_1: %p\n", &A::func_1);
    printf("A::func_2: %p\n", &A::func_2);
    printf("A::func_3: %p\n", &A::func_3);
    printf("A::func_4: %p\n", &A::func_4);
    std::cout << std::endl;
    
    return 0;
}

  main函数中,首先获取普通函数func_0的函数指针,通过std::cout打印;然后获取类中成员函数的函数指针,通过std::cout打印;再分别使用union_castasm_cast获取类成员函数地址,通过std::cout打印;最后直接通过printf打印类中函数指针。

实验结果

在这里插入图片描述

  图中可以看到在win32平台上,普通成员函数和类静态成员函数通过函数指针能够直接打印出函数地址。类中的成员函数通过std::cout打印出来都是1。是由于<<操作符没有对void(__thiscall A::*)()类型进行重载,编译器将其转换为了bool类型,所以输出为1,对于类的静态函数,调用方式不是__thiscall,有对应的重载,所以能正常打印出地址。再看最后printf的输出能够打印出所有类中函数,是因为它能接收任意参数。
  接着分析union_castasm_cast,对于类中的普通函数和静态函数两者打印的地址是一样的,对于虚函数两者打印的却不一样,不确定哪一个是正确的,或者两者都是错误的。于是通过汇编来看看。

在这里插入图片描述

  这里的call eax表示调用虚函数func_3,这里eax寄存器的地址值为0055154B,和asm_cast打印的值是一致的,看起来union_cast在VS2017环境下对虚函数地址转换是有问题的。由于查阅了一些资料显示通过union_cast转换虚函数得到的应该是偏移量,与这里的实验结果不符合,于是在MacOS下Clang编译器环境下重复实验,得到如下结果。

在这里插入图片描述

  图中显示所有函数通过std::cout打印出来都是1,猜测是由于Clang编译器中<<操作符没有重载函数指针类型。通过union_castprintf能够打印出地址,对于类中的普通函数和静态函数打印出的是绝对地址,对于虚函数打印出的是在虚表中的偏移量。与win32平台测试结果差异较大。

总结

  • 如果项目中需要使用函数地址,最好在对应平台上实际测试后采用合适的方式。
  • 从测试结果来看,union_cast在win32下VS2017和MacOS下Clang环境下获取类的普通函数和静态函数地址都没问题;win32上获取虚函数地址结果存疑,不确定获取的地址指向何处;MacOS上获取虚函数地址是偏移量,需要结合虚表指针计算出实际地址。
  • asm_cast在win32下VS2017环境获取类的普通函数和静态函数没问题,获取虚函数也是汇编实际跳转地址。MacOS上架构不同没有测试asm_cast

更多内容关注公众号:C++学习与探索,欢迎留言讨论,创作不易,感谢点赞、关注和收藏

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值