【问题标题】:C++ vector insert implementation crashC++向量插入实现崩溃
【发布时间】:2022-01-14 10:51:24
【问题描述】:

我试图重现向量的行为,当我尝试使用vector::insert(iterator, size_type, const T &) 时发生了奇怪的崩溃,我的代码如下所示:

iterator    insert(iterator pos, size_type count, const T &value) {
    return _M_insert_size(pos, count, value);
}

//with
iterator    _M_insert_size(iterator pos, size_type count, const T &value) {
    const size_type size = _size + count; // get the new size

    if (_capacity < size) reserve(size); // reserve if larger than capacity
    // here `end()` is still using old size
    std::copy(pos, end(), pos + count); // move [pos;end()[ to (pos + count)
    std::fill(pos, pos + count, value); // fill [pos;(pos + count)[ with value
    _size = size; // set the new size
    return pos;
}

//and
void        reserve(size_type new_cap) {
    if (new_cap > max_size()) throw std::length_error(std::string("vector::") + __func__);

    if (new_cap > _capacity) {
        T   *ptr = _allocator.allocate(new_cap);
        std::copy(begin(), end(), ptr);
        _allocator.deallocate(_array, _capacity);
        _capacity = new_cap;
        _array = ptr;
    }
}

//and
iterator                begin(void) { return _array; }
iterator                end(void) { return _array + _size; }

我的代码看起来是合法的,但我遇到了这个崩溃

munmap_chunk(): invalid pointer
[1]    3440 abort (core dumped)  ./build/test

在使用 valgrind 时,我在 std::copy 处读取无效,但过去四个小时我一直在努力,但没有发现哪个值或参数有误。崩溃发生在这次测试中:

            ft::vector< int >   v(10, 42);
            std::vector< int >  r(10, 42);

            v.insert(v.begin(), 5UL, 1);
            r.insert(r.begin(), 5UL, 1);

【问题讨论】:

  • 注意reserve 如何使迭代器失效,包括pos 所指向的迭代器。
  • 请注意,allocate(new_cap) 不会启动任何数组元素的生命周期。使用std::copy 将尝试分配给这些未初始化的对象,即UB。您需要首先在此存储中创建实际对象。在释放旧存储之前,您也无法销毁元素。 int 很好,但对于非平凡的类型会有问题。见constructdestroy

标签: c++ vector iterator


【解决方案1】:
    if (_capacity < size) reserve(size); // reserve if larger than capacity
    // here `end()` is still using old size
    std::copy(pos, end(), pos + count); // move [pos;end()[ to (pos + count)
  • 如果你正在调试这个,你应该知道你是否在这里调用了reserve(),对吧?因为您正在单步执行该函数。

    • 如果您已经知道这一点,那么在调试时应该注意这一点。如果你这样做了,它应该在问题中。
  • 而且由于您正在编写自己的 std::vector::reserve,因此您知道它使包括 pos 在内的所有迭代器无效,因为您的实现总是分配新的存储空间。

    • 如果您想添加调试模式来检测此类内容,可以向容器和迭代器添加生成计数器,并在每次无效操作时增加容器的生成计数器。
  • 如果您在 valgrind 中获得无效读取,它还应该告诉您最初分配内存的位置以及释放的位置。

    • 如果没有,请检查其选项。如果确实如此,那么该信息也应该在问题中。

因此,最近的解决方法是在(可能)调用reserve() 之前写入const auto pos_offset = pos - begin();,然后使用pos_offset 恢复正确的迭代器。

其他问题是:

  • 前导下划线后跟大写字母(如_M)的标识符保留用于实现。您的标准库中提供的std::vector 是实现的一部分,但您的代码不是。
  • _allocator.deallocate 不会破坏数组元素,_allocator.allocate 不会构造它们。参见std::allocator example codestd::construct_atstd::destroy_n 的使用:
        S* s = allocator.allocate(n); // may throw
        for (std::size_t i{}; i != n; ++i) {
        //  allocator.construct(&s[i], i+42); // removed in C++20
            std::construct_at(&s[i], i+42);   // since C++20
        }
        std::destroy_n(s, n);
        // or loop over allocator.destroy(&s[i]); before C++17
        allocator.deallocate(s, n);
    

【讨论】:

  • 你说我的实现总是分配新存储的方式让我问,如果不使用 std::allocator_traits,我们怎么能实现它?因为没有realloc 方法。但是感谢您的帖子,它回答了这个问题。我太习惯了 C x)
  • 您原则上可以选择在std::is_trivial_v&lt;T&gt; == true 时使用realloc,但除此之外,唯一的选择是特定于平台的东西,例如mmap
猜你喜欢
  • 1970-01-01
  • 2016-04-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-08
  • 1970-01-01
相关资源
最近更新 更多