std::copy和std::vector问题

8

我理解为什么这会导致段错误:

#include <algorithm>
#include <vector>
using namespace std;

int main()
{
    vector<int> v;
    int iArr[5] = {1, 2, 3, 4, 5};
    int *p = iArr;

    copy(p, p+5, v.begin());

    return 0;
}

但是为什么这不会导致段错误?

#include <algorithm>
#include <vector>
using namespace std;

int main()
{
    vector<int> v;
    int iArr[5] = {1, 2, 3, 4, 5};
    int *p = iArr;

    v.reserve(1);
    copy(p, p+5, v.begin());

    return 0;
}

@KennyTM 第二个保留空间用于1个元素。 - BЈовић
有一个与C++中不同类型的未定义行为相关的好答案:https://dev59.com/nnRC5IYBdhLWcg3wP-Zh#367662 - user184968
7
你为什么不直接使用copy(p, p+5, back_inserter(v));,避免所有这些麻烦呢? - John Dibling
3
在这种情况下,v.assign(p, p+5)的意思是将指针p到p+5之间的元素赋值给向量v。 - stefaanv
4
或者构造函数:vector<int> v(p, p+5); (说明:该句为 C++ 代码,意思是通过指针 p 构造一个包含五个整型元素的 vector 对象 v) - Martin York
7个回答

11

两者都是错误的,因为你正在将内容复制到一个空向量中,而且复制操作需要保证已经有足够的空间进行插入。容器本身不会自动调整大小。在这种情况下,你可能需要使用 back_insert_iteratorback_inserter

copy(p, p+5, back_inserter(v));

7

这是未定义的行为 - reserve() 分配了至少一个元素的缓冲区,但该元素未初始化。

因此,如果缓冲区足够大,则可以访问第一个元素之外的元素,但如果缓冲区不够大,则可能会出现问题而你没有注意到。

最重要的是 - 不要这样做。只访问在 vector 实例中合法存储的元素。


在复制元素之前,我更喜欢调用resize()而不是reserve() - 否则您无法迭代元素,size()仍将返回0,而且下一个push_back()将开始覆盖您复制的内容。 - AshleysBrain
我明白了。我应该使用resize()而不是reserve()。这两者有什么区别? - nakiya
@nakiya: 你需要友好地请求vector分配足够大的缓冲区,否则它是未定义行为。 - sharptooth
这就是你可以合法访问的元素数量 - 不,如果你只是“reserve”,那么你不能合法访问它们。在vector<int>的情况下,可能很难看出为什么,但是如果向量是类类型,则如果你只做了“reserve”,则它们不会被构造,因此即使赋值也可能无法正常工作。不过,无论类型如何,标准都禁止这样做。实现允许分配内存,但仍然不允许从begin()返回指针/迭代器。 - Steve Jessop
@Steve Jessop: 至少可以说很令人困惑。 :) 编辑:明白了。 - nakiya
@Steve Jessop:是的,你说得对。即使类型存储是一些T*指针,仅仅读取它们也会导致未定义行为。我已经更新了答案。 - sharptooth

3
但是为什么这不会导致段错误呢?
因为星星对齐了。或者你在调试中运行,编译器做了一些“帮助”你的事情。底线是你正在做错的事情,并且跨越到了未定义行为的黑暗和不确定的世界。你在向 vector 中保留了一个位置,然后尝试将 5 个元素塞入保留的空间中。糟糕。
你有三个选择。按我个人的喜好顺序:
1)使用专门为此设计的 back_insert_iterator。它由 #include 提供。语法有点奇怪,但幸运的是,也提供了一个简化的快捷方式,即 back_inserter:
#include <iterator>
// ...
copy( p, p+5, back_inserter(v) );

2) assign将元素分配给向量。我稍微不太喜欢这种方法,因为assignvector的成员,这让我觉得比使用来自algorithm的东西略微不太通用。

v.assign(p, p+5);

3) reserve 函数可以预留足够的空间,然后再复制元素到 vector 中。如果其他方法都失败了,这是最后的办法。它依赖于 vector 存储的连续性,因此不是通用的方法,而且这种方法感觉像是通过后门将数据放入 vector 中。


2

这是错误的!即使在示例中运行良好,访问您未拥有的内存也是未定义的行为。 我认为原因是 std::vector 会保留多个元素。


0

因为你很不幸。访问未分配的内存是未定义行为。


0

很可能是因为一个空向量根本没有分配任何内存,所以你试图写入一个空指针,这通常会导致立即崩溃。在第二种情况下,它至少分配了一些内存,你很可能正在覆盖数组的末尾,这可能会导致C++中的崩溃。

两者都是错误的。


0

顺便说一下,即使是将1个元素复制到向量中(或者保留5个元素然后这样复制),也是错误的。

最可能不会发生段错误的原因是实现者认为为了以后可能需要扩展它而分配仅用于1个元素的内存将会效率低下,所以他们可能为16或32个元素分配了足够的内存。

首先执行reserve(5),然后直接写入5个元素可能不会导致未定义行为,但是是不正确的,因为向量还没有逻辑大小为5,复制几乎是“浪费”的,因为向量仍然声称大小为0。

有效的行为是reserve(5),插入一个元素,将其迭代器存储在某个地方,插入4个更多的元素并查看第一个迭代器的内容。reserve()保证迭代器在向量超过该大小或进行诸如erase()、clear()、resize()或另一个reserve()的调用之前不会失效。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接