美文网首页
解决c++中包含指针字段的自定义类使用vector产生的内存错误

解决c++中包含指针字段的自定义类使用vector产生的内存错误

作者: 江海小流 | 来源:发表于2019-07-21 17:29 被阅读0次

场景描述

先从一个例子开始吧

# include <iostream>
# include <vector>
# include <cstring>

class StringItem {
    public:
        char* data;
        int length;

        StringItem(const char *data) {
            this->length = strlen(data);
            this->data = new char[this->length + 1];
            for (int i = 0; i < this->length + 1; i++) this->data[i] = data[i];
        }

        StringItem(const StringItem& ins) {
            this->length = ins.length;
            this->data = new char[this->length + 1];
            for (int i = 0; i < this->length + 1; i++) this->data[i] = ins.data[i];
        }

        ~StringItem() {
            delete[] this->data;
        }

        std::string toString() {
            return std::string(this->data);
        }
};

int main(void) {
    std::vector<StringItem> v;
    StringItem item_1("Hello");
    StringItem item_2("Big");
    StringItem item_3("Mom");
    v.push_back(item_1);
    v.push_back(item_2);
    v.push_back(item_3);
    std::cout << "A" << std::endl;


    std::cout << "Before erase" << std::endl;
    for (int i = 0; i < v.size(); i++) {
        std::cout << i << " " << v[i].toString() << std::endl;
    }

    v.erase(v.begin());

    std::cout << std::endl << "After erase" << std::endl;
    for (int i = 0; i < v.size(); i++) {
        std::cout << i << " " << v[i].toString() << std::endl;
    }

    return 0;
}

上述代码申明了一个类StringItemmain函数中的测试代码尝试新建一个元素类型为StringItemvector,在插入几条数据后再删除一个数据,分别输出如下(如果要在vector中使用StringItem,必须将拷贝构造函数写好):

➜  cpp ./main
A
Before erase
0 Hello
1 Big
2 Mom

After erase
0 Big
1 @�'��U

从结果中发现,vector中最后一个元素中指针指向的数据变了,而且像是指向一块随机的内存。

分析

问题出在哪呢?

  1. 应该是vector中最后一个元素的指针指向的内存被 delete[] 了。
  2. 删除的是第二个,为什么第三个也会被删除呢?

为了解决这个问题,可以用gdb看看在erase时,具体发生了

在上述代码的47行(erase)打上一个端点

(gdb) b 47
Breakpoint 1 at 0x1182: file main.cpp, line 47.
(gdb) r
Starting program: /home/micl/Projects/VSCodeProjects/works/cpp/main 
A
Before erase
0 Hello
1 Big
2 Mom

Breakpoint 1, main () at main.cpp:47
47          v.erase(v.begin());

通过多次step后,发现执行删除的主要代码在这里

154         _M_erase(iterator __position)
155         {
156           if (__position + 1 != end())
157             _GLIBCXX_MOVE3(__position + 1, end(), __position);
158           --this->_M_impl._M_finish;
159           _Alloc_traits::destroy(this->_M_impl, this->_M_impl._M_finish);
160           return __position;
161         }

可以发现erase的实现方式是:通过把待删除后面所有的元素往前移一个位置,再删除掉最后一个元素。

这样是没有问题的,那么问题出在哪里呢?进一步,将vector存放的数据打印出来发现:

(gdb) p *this
$1 = std::vector of length 3, capacity 4 = {{data = 0x55555576aef0 "Hello", length = 5}, {data = 0x55555576afd0 "Big", length = 3}, {data = 0x55555576aed0 "Mom", length = 3}}
(gdb) n
157             _GLIBCXX_MOVE3(__position + 1, end(), __position);
(gdb) n
158           --this->_M_impl._M_finish;
(gdb) p *this
$2 = std::vector of length 3, capacity 4 = {{data = 0x55555576afd0 "Big", length = 3}, {data = 0x55555576aed0 "Mom", length = 3}, {data = 0x55555576aed0 "Mom", length = 3}}
(gdb) n
159           _Alloc_traits::destroy(this->_M_impl, this->_M_impl._M_finish);
(gdb) p *this
$3 = std::vector of length 2, capacity 4 = {{data = 0x55555576afd0 "Big", length = 3}, {data = 0x55555576aed0 "Mom", length = 3}}
(gdb) n
160           return __position;
(gdb) p *this
$4 = std::vector of length 2, capacity 4 = {{data = 0x55555576afd0 "Big", length = 3}, {data = 0x55555576aed0 "@\257vUUU", length = 3}}

_GLIBCXX_MOVE3 调用后,vector最后两个元素指向了同一个地址,之后_Alloc_traits::destroy便会把"Mom"所在的内存释放掉,所以便出现了这样的错误。

解决方法

要解决这个问题,要么杜绝掉纯内存复制的情况(如果要用vector,显然不行),要么在多个StringItem中的指针指向同一块内存时,不能每一个StringItem都释放这块内存。c++ 里面有一个 shared_ptr 可以用于解决这样的问题。

更改后代码如下:

# include <iostream>
# include <vector>
# include <cstring>
# include <memory>

class StringItem {
    public:
        std::shared_ptr<char> data;
        int length;

        StringItem(const char *data): length(strlen(data)), 
                                      data(new char[this->length + 1], [](char *p) {delete[] p;}) {
            for (int i = 0; i < this->length + 1; i++) this->data.get()[i] = data[i];
        }

        StringItem(const StringItem& ins): length(ins.length), 
                                           data(new char[this->length + 1], [](char *p){delete[] p;}) {
            for (int i = 0; i < this->length + 1; i++) this->data.get()[i] = ins.data.get()[i];
        }

        ~StringItem() {
        }

        std::string toString() {
            return std::string(this->data.get());
        }
};

int main(void) {
    std::vector<StringItem> v;
    StringItem item_1("Hello");
    StringItem item_2("Big");
    StringItem item_3("Mom");
    v.push_back(item_1);
    v.push_back(item_2);
    v.push_back(item_3);
    std::cout << "A" << std::endl;


    std::cout << "Before erase" << std::endl;
    for (int i = 0; i < v.size(); i++) {
        std::cout << i << " " << v[i].toString() << std::endl;
    }

    v.erase(v.begin());

    std::cout << std::endl << "After erase" << std::endl;
    for (int i = 0; i < v.size(); i++) {
        std::cout << i << " " << v[i].toString() << std::endl;
    }

    

    return 0;
}

shared_ptr 会记录有多少个指针指向了对应的内存区域,当没有指针指向这块区域时便会释放掉这一块内存,因此就不需要在析构函数中delete[] data了,同时也可以上述问题。

相关文章

  • 解决c++中包含指针字段的自定义类使用vector产生的内存错误

    场景描述 先从一个例子开始吧 上述代码申明了一个类StringItem,main函数中的测试代码尝试新建一个元素类...

  • 标准模板库-vector

    标准模板库-vector 1. vector简介 vector为C++的STL中的模板数组容器。在使用时需要包含#...

  • VC++ 野指针,空指针等内存问题错误码

    最近使用 VS 开发 c++ 的 dll ,发现 vc++ 对野指针、空指针、未初始化内存等导致的内存错误报有特定...

  • 2018-01-12

    #漫漫长路C++(一):C++中的vector容器 包含头文件 vector是一个类模板,不是类或者函数。 定义与...

  • 智能指针

    指针的危害 指针未初始化 野指针 内存泄漏 参考阅读C/C++指针使用常见的坑 智能指针分类 本质:将指针封装为类...

  • C++ 面向对象 一

    C++ 面向对象 c++创建对象的时候如果使用new运算符,将会返回返回一个指针,指向堆中的内存地址 类,对象类定...

  • c++学习之vector(容器)、priority_queue(

    c++学习之vector(容器) (转刘同学_0116) 使用vector需要包含头文件#include

  • C++ 指向类的指针

    原文地址:C++ 指向类的指针 一个指向 C++ 类的指针与指向结构的指针类似,访问指向类的指针的成员,需要使用成...

  • 1.2.09_C++ 指向类的指针

    C++ 类 & 对象 一个指向 C++ 类的指针与指向结构的指针类似,访问指向类的指针的成员,需要使用成员访问运算...

  • 《C++ Primer Plus》第12章学习笔记

    类和动态内存 1. 动态内存和类 本章先从一个错误的字符串类设计来揭示在C++类设计中可能存在的问题,特别是在使用...

网友评论

      本文标题:解决c++中包含指针字段的自定义类使用vector产生的内存错误

      本文链接:https://www.haomeiwen.com/subject/fjfvlctx.html