C++关于vector底层模拟实现扩容的时候,使用memcpy拷贝数据导致的野指针和数据丢失问题及解决办法

知识引入

在C++中,vector通常使用动态数组实现,即在需要存储更多元素时,将动态分配一块更大的内存空间,将原有元素复制到新的内存位置,然后释放原有内存空间。在这个过程中,如果使用memcpy拷贝数据,则会导致野指针和数据丢失问题。

为什么使用memcpy会导致野指针?

我们实现reserve接口来扩容(memcpy拷贝数据):

void reserve(size_t n)
{
	if (n > capacity())
	{
		size_t oldSize = size();
		T* tmp = new T[n];
		if (_start)
		{
			memcpy(tmp, _start, sizeof(T)*oldSize);//这是错误的
			delete[] _start;
		}
		_start = tmp;
		_finish = tmp + oldSize;
		_endofstorage = _start + n;
	}
}

一开始我们插入数据的时候有一个缺省值的capacity值,一般情况下是4个,开始的扩容因为里面是空的,所以不需要拷贝数据
在这里插入图片描述
插满以后我们再想继续插入,那么就又需要扩容,但是这个扩容需要拷贝数据

传统方法:
在这里插入图片描述
到这里你会发现,memcpy拷贝一维数组的数据没有问题,但是在拷贝二维数组的时候问题就体现出来了

在这里插入图片描述
你可能会认为,这样指向原数据也没有问题啊?
 但是我们在memcpy完以后delete_start指针,会自动调用析构函数,将指向的的数据的空间自动释放了,这个时候我们新扩容以后_start里面的指针指向的就是一个个野指针了

在这里插入图片描述
 最后的结果就是这样,可能直接打印一堆乱码,然后越界,也可能前四个都是乱码,新插入的数据能打印出来,但也照样越界。

这是内置类型的二维数组vector,如果是自定义类型的的vector呢?
在这里插入图片描述
错误原因:因为在动态数组扩容时,原有元素的地址已经被释放,而memcpy是一个内存拷贝函数,它只是简单地将源地址的数据复制到目标地址,不会对原有内存进行越界检查,也不会更新指针的值。因此,复制后的数据指针可能会指向已经释放的内存,导致野指针问题;同时,如果原有内存中存在指向其他对象的指针或引用,这些指针或引用也可能指向被释放的内存,导致数据丢失问题。

解决办法

解决办法就是不能使用浅拷贝memcpy只拷贝表面数据,要使用深拷贝,另外再开辟空间。
(C++11有更好的办法,现在不考虑)

这里简单举一个例子:

void reserve(size_t n)
{
	if (n > capacity())
	{
		size_t oldSize = size();
		T* tmp = new T[n];
		if (_start)
		{
			//memcpy(tmp, _start, sizeof(T)*oldSize);
			for (size_t i = 0; i < oldSize; ++i)
			{
				tmp[i] = _start[i];
			}

			delete[] _start;
		}
		_start = tmp;
		_finish = tmp + oldSize;
		_endofstorage = _start + n;
	}
}

我们使用"="前提是我们已经实现了拷贝构造函数,且是深拷贝,使用“=”号底层就是拷贝构造函数
在这里插入图片描述

解决办法2:
使用std::copy()函数代替memcpy()函数进行数据复制。std::copy()函数是一个通用的算法函数,它可以确保在复制时不会出现野指针和数据丢失问题。另外,还可以使用std::move()函数来移动元素,而不是复制它们。移动元素可以更快地完成扩容操作,并且不会出现指针和引用的问题。

  • 使用memmove而不是memcpy是一个更安全的功能,可用于将数据复制到未分配的位置。这将防止野生指针问题:
#include <vector>
#include <cstring>

void expand_vector(std::vector<int>& v) {
  int capacity = v.capacity();
  int* new_buffer = new int[capacity * 2];

  // Copy
  memmove(new_buffer, v.data(), capacity * sizeof(int));

  // Delete
  delete[] v.data();
  v.data() = new_buffer;
  v.capacity() = capacity * 2;
}
  • 使用可增长的阵列:可增长数组是一种数据结构,无需复制数据即可扩展到更大的大小。这将防止数据丢失问题。
    下面是如何使用可增长阵列来避免数据丢失问题的示例:
#include <vector>
#include <algorithm>

class GrowableArray {
 public:
  GrowableArray() {}

  void push_back(int value) {
    if (size() == capacity()) {
      resize(capacity() * 2);
    }
    //在数组的末尾插入新值
    data()[size()] = value;
  }

  int size() const { return std::end(data()) - std::begin(data()); }

 private:
  int* data_ = nullptr;
  int capacity_ = 0;

  void resize(int new_capacity) {
    int* new_data = new int[new_capacity];

    // Copy
    std::copy(data_, data_ + capacity_, new_data);

    // Delete
    delete[] data_;
    data_ = new_data;
    capacity_ = new_capacity;
  }
};

如有错误或者不清楚的地方欢迎私信或者评论指出🚀🚀

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

侠客cheems

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值