C++逆向分析——友元、内部类、命名空间和static
友元
友元可以理解为:朋友、元素;老师认为这个友元是C++中的一个垃圾,因为友元的存在破坏了面向对象的封装性,不推荐使用,之所以有这个章节是因为有人不了解这个概念。
注意:在一些新版本的C++编译器里面已经不再提供类似于友元这样的特性了。
大家都知道在C++中对象的私有成员,外部是无法访问的,这在面向对象中是很正常的,如果你想访问按照正常的逻辑你应该在类中声明成员函数去增删改查这个私有成员。
友元的诞生就是破坏了这个封装性,让你可以在外部去使用这个私有成员。
友元的语法就是:friend 返回类型 函数名(参数列表)
老师个人认为C++之所以有友元是因为这是C++作者面向过程的一种妥协,这是因为C++是先有的C才有的C++,而很多人已经很熟悉C语言的语法了,你这时候推出一个新的概念,是对开发者的不友好(代码重构、学习代码),所以要妥协。
刚刚举例说明的是友元函数,就是告诉编译器这个函数是我的朋友,可以访问我的任何成员。
除了友元函数,还有加强版的垃圾:友元类,如下是语法格式:
class
CObject {
friend
class
Test;
private
:
int
x;
public
:
CObject() {}
CObject(
int
x) {
this
->x = x;
}
};
class
Test {
public
:
void
Fun(CObject* c) {
printf(
"%d \n"
, c->x);
}
};
以上代码就是告诉编译器,Test是CObject的朋友,所以Test可以直接访问CObject的私有成员;但是要注意的是,这种访问是单向的,Test可以访问CObject,但反之则不行。
内部类
内部类,简单的说就是在别的类里面定义的类就是内部类。
-
大小:当前类和其内部类不存在包含关系,不会影响当前类的大小;
-
关系:两者之间不存在什么特殊关系,也无法访问对方的私有成员;
-
声明:声明创建内部类需要使用格式 → 类::内部类 名称;;
-
权限:如果你的内部类不想外部创建对象,那就定义到private内即可;而如果你用到这个内部类的情况非常的少,例如你只有一个成员函数需要使用到,那么完全可以定义到这个函数内;
-
作用:如果我们需要实现一些功能而用到一个类,但是其他的模块、类用不到,我们可以就把这个类写到当前所需要使用类中。(隐藏)
在我的vs2022里实验下:
#include <stdio.h> class Date { public: int year; int month; int day; class Time { public: int hour; int minute; int secend; }; }; void main() { Date d; int s = sizeof(d); printf("%d\n", s); return; }
答案是12. 因为没有使用Time。加一个Time t成员size就是24了。
命名空间
命名空间主要是用来解决命名冲突的问题;比如你定义了一个函数叫Fun,而如果你还想定一个函数也叫Fn,这种情况下就可以使用命名空间来解决这个问题。
命名空间的关键词:namespace,其语法格式如下:
namespace 名称x {
// 全局变量
// 函数
// 类
}
namespace 名称y {
// 全局变量
// 函数
// 类
}
调用也很简单,使用格式:命名空间名称::函数\变量\类
如果我们命名空间内的东西非常多,但是你要调用就必须要加上前缀,这时候你可以在代码的首行写:
using namespace x;
但这也会引起命名冲突,如果命名空间x里面有一个Test函数,但是在正常代码中也存在Test函数,你想调用的是本身的该怎么办呢?实际上在C++中,你定义的所有东西都存在一个全局命名空间,而想调用可以使用如下语法:
::Test();
#include <iostream> using namespace std; // 第一个命名空间 namespace first_space { void func() { cout << "Inside first_space" << endl; } } // 第二个命名空间 namespace second_space { void func() { cout << "Inside second_space" << endl; } } int main() { // 调用第一个命名空间中的函数 first_space::func(); // 调用第二个命名空间中的函数 second_space::func(); return 0; }
看下反汇编代码:
// 调用第一个命名空间中的函数 first_space::func(); 00CF2671 E8 4B EC FF FF call first_space::func (0CF12C1h) // 调用第二个命名空间中的函数 second_space::func(); 00CF2676 E8 4C EB FF FF call second_space::func (0CF11C7h)
可以看到就是编译器给函数加了一个名字而已!
static关键字
用static就是一个全局变量,只不过它是一个私有的全局变量。
在面向过程(没有对象的概念,用函数)中的static:
void
Func() {
static
char
strBuffer[
0x10
];
strcpy(strBuffer,
"test"
);
}
用static声明的全局变量,只有当前函数能访问;我们可以看下反汇编代码来论证这是一个全局变量:
如上图所示的这个内存地址,很明显就是一个全局区的地址(这也就表示相同变量只能申请一次,不再接受第二次申请,也就表示全局变量应用场景,你可以用这个关键词来实现)。
在我的vs2022里试验下:
#include <stdio.h> #include <string.h> void Func() { static char strBuffer[30] = "hello world"; strcpy_s(strBuffer, "test"); } int main () { Func(); return 0; }
看下反汇编代码:
在odb里看到的:
注意看到那一堆的push offset啥的 不要慌,就是一个static变量的内存地址。指向hello world
面向对象设计中的static之静态数据成员:
面向对象设计中的static之静态成员函数:
static的经典应用:单子模式;有些时候我们希望定义的类只能有一个对象存在,这时候你该如何限制呢?实现思路有两个:
-
禁止对象被随便创建
-
保证对象只有一个存在