美文网首页
解决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产生的内存错误

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