selph
selph
Published on 2021-09-29 / 614 Visits
0
0

逆向分析:C++对象的构造/析构/传参/返回

分析C++对象的四种情况:构造函数,析构函数,作为参数传递,作为返回值返回

构造函数

局部对象

看看局部对象是怎么生成的

代码:

#include <stdio.h>
#include <stdlib.h>
class rk
{
public:
	rk();
	~rk();
private:
	int age;
};
rk::rk()
{
	age = 20;
}
rk::~rk()
{
}
int main()
{
	//局部对象 无参构造函数
	rk mk;
	system("pause");
	return 0;
}

分析:

主函数:传入局部变量对象地址进入函数

image-20210929091037910

进入构造函数:在函数内部对地址内容进行赋值,改变实参

image-20210929091104380

堆对象

看看堆对象是怎么生成的

代码:

#include <stdio.h>
#include <stdlib.h>
class rk
{
public:
	rk();
	~rk();
private:
	int age;
};
rk::rk()
{
	age = 20;
}
rk::~rk()
{
}
int main()
{
	//堆对象
	rk *p = new rk();
	system("pause");
	return 0;
}

分析:

主函数:ida可以识别出来new函数,这里有一个判断,如果new成功了则调用构造函数,new失败了则不进行构造

构造函数的功能是给指定地址塞入对象的成员变量

image-20210929091938918

构造函数:

image-20210929092123355

和局部对象没啥区别,主函数区别主要体现在了new的相关判断上,如果new失败了会不进行构造函数的调用

析构函数

局部对象

看看局部对象的析构函数是怎么执行的

代码:

#include <stdio.h>

class Person {
public:
	Person() {
		age = 1;
	}
	~Person() {
		printf("~Person()\n");
	}
private:
	int age;
};

int main(int argc, char* argv[]) {
	Person person;
	return 0;  //退出函数后调用析构函数
}

分析:

主函数:创建了个对象,然后没用,对象就析构了

image-20210929143006335

析构函数:析构函数也没啥用,printf了字符串就结束了,就像是调用普通函数一样

image-20210929143034451

堆对象

看看堆对象的析构函数是怎么执行的

代码:

#include <stdio.h>

class Person {
public:
	Person() {
		age = 20;
	}
	~Person() {
		printf("~Person()\n");
	}
	int age;
};

int main(int argc, char* argv[]) {
	Person *person = new Person();
	person->age = 21;
	printf("%d\n", person->age);
	delete person;
	return 0;
}

分析:

主函数:就看析构那一块,这里会先判断对象地址是否为空,如果为空就跳过,这里注释的析构函数还不是真正的析构函数

image-20210929144054737

析构代理函数:这里就用到了主函数里push的不知道干嘛的数字,应该是个标识符,标识是否需要delete释放堆空间,这里把对象地址拿出来执行虚构函数,然后根据标识符进行delete释放空间

image-20210929144202017

对象作为参数和返回值

参数对象

看看对象作为参数是怎么传递的

代码:

#include <stdio.h>
#include <string.h>

class Person {
public:
	Person() {
		name = NULL;
	}
	Person(const Person& obj) {
		int len = strlen(obj.name);
		this->name = new char[len + sizeof(char)];
		strcpy(this->name, obj.name);
	}
	~Person() {	
		if (name != NULL) {
			delete[] name;
			name = NULL;
		}
	}


	void setName(const char* name) {
		int len = strlen(name);
		if (this->name != NULL) {
			delete[] this->name;
		}
		this->name = new char[len + sizeof(char)];
		strcpy(this->name, name);
	}
public:
	char * name;
};

void show(Person person) {
	printf("name:%s\n", person.name);
}

int main(int argc, char* argv[]) {
	Person person;
	person.setName("Hello");
	show(person);
	return 0;
}

分析:

主函数:把对象作为参数的时候,会在调用函数之前先调用拷贝构造函数,这里把对象往esp的位置上复制了一份,然后进行带对象参数的函数调用

image-20210929134928008

拷贝构造函数:新对象地址存在ecx里,原对象地址在栈中参数里,最后复制完成后,把新对象地址放在eax里,实际上等退出拷贝构造函数后,新对象的地址就是esp栈顶的位置

image-20210929135115464

对象参数的函数:进入带对象参数的函数的时候,会从栈中获取对象地址

image-20210929135244328

最后会执行析构函数:析构函数没啥好说的,就是把对象申请的空间给释放掉

image-20210929135347367

返回对象

看看对象作为返回值是怎么返回的

代码:

#include <stdio.h>
#include <string.h>

class Person {
public:
	Person() {
		name = NULL;
	}
	Person(const Person& obj) {
		int len = strlen(obj.name);
		this->name = new char[len + sizeof(char)];
		strcpy(this->name, obj.name);
	}
	~Person() {		
		if (name != NULL) {
			delete[] name;
			name = NULL;
		}
	}


	void setName(const char* name) {
		int len = strlen(name);
		if (this->name != NULL) {
			delete[] this->name;
		}
		this->name = new char[len + sizeof(char)];
		strcpy(this->name, name);
	}
public:
	char * name;
};

Person getObject() {
	Person person;
	person.setName("Hello");
	return person;
}

int main(int argc, char* argv[]) {
	Person person = getObject();
	return 0;
}

分析:

主函数:这里只是把对象的空间清理出来了,但没有用构造函数,就进入getObject函数了,对象地址存在栈中传入

image-20210929141432387

getObject函数:首先创建临时对象,使用临时对象执行setName函数后,进行返回流程,返回则是把参数中的对象地址拿出来,进行调用拷贝构造函数,相当于是把临时对象复制给了原对象

image-20210929141613269

总结

对象通过构造函数进行初始化,当对象里有指针的时候,进入构造函数前会清空一片内存,然后才进入构造函数,构造函数的功能就是给指定地址赋值对象所需要的各种成员变量的值。

对象生命周期结束的时候会调用析构函数,析构函数用来释放对象里申请的资源,如果没有需要释放的资源,那析构函数就像普通成员函数一样了

当对象作为参数进入函数的时候,会有两个连续调用call,一个是拷贝构造函数,一个是我们调用的函数,这里会将拷贝构造函数拷贝出来的临时变量传递给我们调用的函数用

当对象作为返回值从函数出来的时候,会通过拷贝构造函数来进行给用来接收的对象赋值


Comment