我们知道,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的地址将顺移一个指针的大小。
这些操作大家平时玩玩就好了,千万不要写到工程代码里面去