【转帖】C++逆向学习(三) 移动构造函数
转自:https://xz.aliyun.com/t/5149最近研究了一下C++类的**移动构造函数**,同时也进行了一些逆向分析,过程中碰到一个很奇怪的问题,以此记录
# 相关背景
## 右值引用
右值引用主要是为了解决`C++98/03`遇到的两个问题
> 1. 临时对象非必要的昂贵的拷贝操作
> 2. 模板函数中如何按照参数的实际类型进行转发
本文主要探讨问题1,一些代码尝试和IDA中逆向的分析
学习链接:[从4行代码看右值引用](https://www.cnblogs.com/qicosmos/p/4283455.html),这里就不多说了
## move语义
比如在`vector.push_back(str)`时,`str(类)`作为实参,会复制一份自身成为形参,进入函数调用
而这个过程中就会产生**临时对象**,那么也就会调用**拷贝构造函数**
而如果`vector.push_back(std::move(str))`,就可以匹配**移动构造函数**,省去这个拷贝过程以提高效率
> 链接中已经解释的很详细了,不再赘述,总之就是**给将亡值续命**,延长它的生命周期(原本很可能是一个临时变量)
# 代码分析
接下来的部分内容可以作为上一篇文章(https://xz.aliyun.com/t/4933)的补充,在分析**移动构造函数**时又学到了一些之前没有注意过的`vector`的细节
## Str类源码
```
#include<iostream>
#include<string.h>
#include<vector>
using namespace std;
class Str {
public:
char* str;
Str(char value[]) {
cout << "Ordinary constructor" << endl;
int len = strlen(value);
this->str = (char*)malloc(len + 1);
memset(str, 0, len + 1);
strcpy(str, value);
}
//拷贝构造函数
Str(const Str& s) {
cout << "copy constructor" << endl;
int len = strlen(s.str);
str = (char*)malloc(len + 1);
memset(str, 0, len + 1);
strcpy(str, s.str);
}
//移动构造函数
Str(Str&& s) {
cout << "move constructor" << endl;
str = s.str;
s.str = NULL;
}
~Str() {
cout << "destructor" << endl;
if (str != NULL) {
free(str);
str = NULL;
}
}
};
//g++ xxx.cpp -std=c++17
```
## 代码1
`main`函数中,不使用`move`语义,会调用拷贝构造函数
```
int main(int argc, char** argv) {
char value[] = "template";
Str s(value);
vector<Str> vs;
vs.push_back(s);
return 0;
}
```
IDA打开如下
!(https://xzfile.aliyuncs.com/media/upload/picture/20190513221326-464a486a-7589-1.png)
简单的流程,甚至`Str`的高亮都是对称的
最初调用`Str`的拷贝构造函数,匹配的是`Str(char value[])`,接着初始化`vector`,然后一次`push_back(s)`
跟进`push_back`
!(https://xzfile.aliyuncs.com/media/upload/picture/20190513221330-48fdb07e-7589-1.png)
一开始仍然是熟悉的判断`vector`的`size & capacity`的关系,最终调用的是这里的复制构造函数
!(https://xzfile.aliyuncs.com/media/upload/picture/20190513221335-4b94d7d6-7589-1.png)
> 注意第一个参数是`this`,是C++成员函数调用时的第一个参数,类指针
运行结果:
!(https://xzfile.aliyuncs.com/media/upload/picture/20190513221340-4ec00390-7589-1.png)
## 代码2
代码2,只`move(s)`
```
int main(int argc, char** argv) {
char value[] = "template";
Str s(value);
vector<Str> vs;
//vs.push_back(s);
//cout<<"-----------------"<<endl;
vs.push_back(move(s));
return 0;
}
```
IDA打开如下:
!(https://xzfile.aliyuncs.com/media/upload/picture/20190513221346-522e3ccc-7589-1.png)
注意到其中的`std::move`,跟进发现其实实现只有一句话
!(https://xzfile.aliyuncs.com/media/upload/picture/20190513221351-5536fbd4-7589-1.png)
也印证了`move`实际上不移动任何东西,唯一的功能是**将一个左值强制转换为一个右值引用**
继续跟进
!(https://xzfile.aliyuncs.com/media/upload/picture/20190513221358-598c6cfa-7589-1.png)
仍然是判断大小和容量的代码,接着调用的是**移动构造函数**
!(https://xzfile.aliyuncs.com/media/upload/picture/20190513221402-5bea3a54-7589-1.png)
运行结果:
!(https://xzfile.aliyuncs.com/media/upload/picture/20190513221406-5e7defd6-7589-1.png)
## 代码3
这段代码实际上只是在`单纯move`之前加上了一句`push_back(s)`,但是运行结果差了很多
作为对`vector逆向学习`的补充
> "我全都要"写法,同时用拷贝构造和移动构造
```
int main(int argc, char** argv) {
char value[] = "template";
Str s(value);
vector<Str> vs;
vs.push_back(s);
cout<<"-----------------"<<endl;
vs.push_back(move(s));
return 0;
}
```
按理来说,输出结果也只应该比代码2多一个`copy constructor`和`destructor`,但实际上多了很多东西
!(https://xzfile.aliyuncs.com/media/upload/picture/20190513221415-63c62c38-7589-1.png)
IDA打开并没有出乎意料的结果,仍然是清晰的两次`push_back`,跟进后也没有什么特别的发现,查看交叉引用也没能找到相关信息
> 为什么在`move`之后还会有一次`copy`,对应的之后又多了一个`desctructor`?
首先,`vector`虽然是值语义,但是`move`过后,既然已经调用了**移动构造函数**,肯定不会再无聊的拷贝一次
在`vs`里调试,输出各个时间点的`capacity`
!(https://xzfile.aliyuncs.com/media/upload/picture/20190513221423-6837120a-7589-1.png)
注意`第一个destructor`和`容量2`的出现时间
跟进源码好久后才发现,多的`copy`的产生原因,是因为`vector`内部动态扩容时,在新开辟的空间上调用了**复制构造函数**
也就是说把原来的一个`Str s`复制到了新内存空间,这个过程并没有调用**移动构造函数**
> 可能这也是写了移动构造函数后,保险起见也要写一个复制构造函数的原因
### 其他
考虑这个问题
> 为什么`vector`内部扩容时,要在新地址调用拷贝构造函数呢?
>
> 之前文章已经分析过,`vector`实际上只存了类型的数据结构
>
> 直接`memcpy(new_memory,old_memory,size)`,再把旧内存空间清零,会造成什么问题?
查了一些资料后发现,扩容是`allocator`的事情,一个可能的实现是**原位new**
而如果直接`memcpy`,会不会出问题取决于`vector`存的类型是否平凡(POD)
POD是`Plain old data structure`的缩写
> 资料提到`shared_ptr`也可能会被影响,取决于引用计数放在哪里
但无论如何,指针的浅拷贝、深拷贝问题值得注意,否则在`vector`内部扩容时,可能2个指针指向同一块内存,析构时会产生严重的错误
> 一个月后的SUCTF会有一道C++底层相关的pwn,欢迎来体验 写的不错学习了。不过代码地方有点小瑕疵,当*this==str,直接就返回*this就行了。没必要再进行下面操作了 向高手,学习一下, 看着有点生涩 学习一下 大佬辛苦了 学习学习{:301_1009:} 受教,谢啦!!! 围观围观。 还可以更加简单 谢谢大佬分享
页:
[1]
2