外部访问C++类的私有方法和私有变量

我们知道,C++类私有方法和变量是不允许通过类实例直接访问的,这样子的操作会导致编译报错。但有没有方法访问到呢?有的。
首先,我们需要知道C++和C语言本质上是没有什么区别的,C++只是语法层面上对C语言的封装。所有C函数,只要有声明和定义,那就可以使用,不存在public和private的区分。C++的public, private和protected限定的作用只在于编译期,当我们进入到运行期的时候,就无所谓public/private/protected了。

然后我们知道,函数都是保存在代码段的,那么C++类的成员函数必然也保存在代码段。但是C++类成员函数和普通的函数有什么区别呢?例子如下:

class Cls
{
public:
    void func() {}
}

上面的Cls类又一个成员函数void func()。而func只能够通过Cls().func()的方式调用,不能直接func()调用。
看起来C和C++的函数有很大的区别?
其实不然,我们知道,C++的成员函数里面可以使用this指针。但是我们上面Cls().func()并没有往func函数中传递this指针,func函数怎么获取的this指针呢?
Cls().func()左边有个Cls实例,也即是说,func()中的this指针就是左边的Cls实例。我们将func函数转换一下:void func(Cls* this)。这种写法就很合乎C语言的写法了。所以我们认为Cls().func()等价于

auto cls = Cls();
func(&cls);

接下来,整个类转换一下:

struct Cls {};
void func(Cls* this) {}

cool!
那么C++语言中类成员变量在内存中怎么保存的呢?如下:

class Cls
{
public:
    int a;
    int b;
}

其实这个不消说,大家都知道这个直接可以转换成C风格:

struct Cls
{
    int a;
    int b;
}

在C语言里面,结构体Cls可以在栈中创建,也可以在堆中创建,anyway,成员a和成员b的地址始终是紧挨着的,而成员a的地址就是整个结构体的起始地址。
好,不多说,进入主题,如何在外部访问C++类的私有方法和私有变量(仅供娱乐,平时写代码勿以为参考)
定义类如下:

class Cls
{
public:
    void pub_func()
    {
        std::cout << "Cls::pub_func" << std::endl;
        pri_func(); // 一定要在一个public函数里调用一下,不然pri_func会被编译器优化干掉
    }

    inline int getA() { return a; }
    inline int getB() { return b; }

private:
    void pri_func()
    {
        a = 15;
        std::cout << "Cls::pri_func" << std::endl;
    }

    int a = 0;
    int b = 0;
};

类Cls提供三个公开方法:pub_func, getA, getB
现在我想要调用私有方法pri_func, 并且还要修改私有成员a和b的值

调用私有方法pri_func
第一步:调用pub_func,并编译生成a.out

int main()
{
    auto cls = Cls();
    cls.pub_func();
}

第二步:获取pri_func函数的真实函数名

nm -A a.out | grep pri_func

得到编译器改名后的pri_func为_ZN3Cls8pri_funcEv
第三步:声明并调用_ZN3Cls8pri_funcEv

extern "C" {
    void _ZN3Cls8pri_funcEv(Cls*);
}

int main()
{
    auto cls = Cls();
    cls.pub_func();
    _ZN3Cls8pri_funcEv(&cls);
}

执行上述代码,可以发现,pri_func中的打印输出了
第四步:观察a的值是否被修改

int main()
{
    auto cls = Cls();
    cls.pub_func();
    _ZN3Cls8pri_funcEv(&cls);
    std::cout << cls.getA() << std::endl;
}

毫无疑问,此时打印出a的值为15
接下我们修改b的值。回溯上面讲的,a的地址就是Cls实例的起始地址,a,b是连续的,所以,其实b的地址就是 &cls + 4bytes。写成代码如下:

int main()
{
    auto cls = Cls();
    int*p = reinterpret_cast<int*>(&cls);
    *(p + 1) = 13;  // p就是a的地址,所以p+1就是b的地址
    std::cout << cls.getB() << std::endl;
}

打印结果就是13
需要注意的就是,如果类中存在虚函数,那么cls的首地址就不是a的地址了,而是记录虚函数表的地址,a,b的地址将顺移一个指针的大小。
这些操作大家平时玩玩就好了,千万不要写到工程代码里面去

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值