为啥 vector::clear 不从向量中删除元素?

Posted

技术标签:

【中文标题】为啥 vector::clear 不从向量中删除元素?【英文标题】:Why doesn't vector::clear remove elements from a vector?为什么 vector::clear 不从向量中删除元素? 【发布时间】:2013-06-21 14:19:50 【问题描述】:

当我在std::vector 上使用clear() 时,它应该会破坏vector 中的所有元素,但实际上并没有。

示例代码:

vector<double> temp1(4);
cout << temp1.size() << std::endl;
temp1.clear();
cout << temp1.size() << std::endl;

temp1[2] = 343.5; // I should get segmentation fault here ....

cout << "Printing..... " << temp1[2] << endl;
cout << temp1.size() << std::endl;

现在,我应该在尝试访问已清除的向量时遇到分段错误,但它会填充那里的值(在我看来这是非常错误的)

结果如下:

4
0
Printing..... 343.5
0

这正常吗?这是一个很难发现的错误,它基本上杀死了我几个月的代码。

【问题讨论】:

如果你想捕捉类似的bug,使用checked容器(gcc可以做到这一点,或者外部stl库等) 内存管理单元产生分段错误,这是一个 C++ 不需要的硬件组件。如果未能获得段错误导致您的程序行为不端,那么您将遇到更严重的问题。 您可以使用at 运算符进行边界检查并给出异常。我建议使用at 而不是[] @KarolyHorvath:默认情况下,MSVC 的库在调试版本中被选中,而在发布版本中未选中。太棒了。 您可以考虑尝试使用 clang 静态分析器分析您的代码:clang-analyzer.llvm.org。我认为它会标记这个错误。 【参考方案1】:

您无权获得分段错误。就此而言,分段错误甚至不是 C++ 的一部分。您的程序正在从向量中删除所有元素,并且您非法访问容器越界。这是未定义的行为,这意味着任何事情都可能发生。确实,发生了一些事情。

【讨论】:

又是 C++ 编译器的话题,UB 上的那种格式游览 HDD :D 从 C++ 标准到充满新手编译器的“类”——“你有权保持沉默”... :-)【参考方案2】:

当您访问向量边界之外时,您会得到未定义的行为。这意味着任何事情都可能发生。随便。

所以你可能会得到旧值、垃圾或段错误。你不能依赖任何东西。

如果您想要边界检查,请使用at() 成员函数而不是operator []。它将抛出异常而不是调用未定义的行为。

【讨论】:

【参考方案3】:

来自 cppreference:

void clear();

从容器中移除所有元素。使引用包含元素的任何引用、指针或迭代器无效。可能使任何过去的迭代器无效。 许多实现在调用clear() 后不会释放分配的内存,实际上保持向量的容量不变。

所以没有明显问题的原因是向量仍然有可用的内存。当然,这只是特定于实现的,而不是错误。此外,正如其他答案所指出的那样,您的程序也确实具有未定义的行为,用于首先访问已清除的内容,因此从技术上讲,任何事情都可能发生。

【讨论】:

高亮部分是由标准保证的,还是只是典型的行为? @sasha.sochka 为什么这很重要? @MarkRansom 该标准只是说它将清除容器中的所有元素。我找不到任何东西(到目前为止)可以保证这种行为。 @0x499602D2,我之所以这么问,是因为 cppreference.com 是一个 wiki,它可能包含不准确的信息。 @0x499602D2 序列容器要求(第 23.2.3 节/表 100)状态 a.clear() - 销毁 a 中的所有元素。使所有引用 a 的元素的引用、指针和迭代器无效,并可能使过去的迭代器无效。 cppreference 是错误的,因为 capacity() 保持不变,而过去的迭代器仍然有效.【参考方案4】:

让我们想象一下你很富有(也许你是或者你不是......无论如何)!

由于您富有,您可以在茉莉雅岛(法属波利尼西亚的向风群岛)购买一块土地。 你很确定这是一个不错的房产,所以你在那个岛上建了一座别墅,然后住在那里。 你的别墅有一个游泳池、一个网球场、一个大车库,还有更多好东西。

一段时间后,您会离开茉莉雅岛,因为您认为那里变得非常无聊。运动多,但人少。 你卖掉了你的土地和别墅,并决定搬到其他地方。

如果您稍后再回来,您可能会遇到很多不同的事情,但您甚至无法确定其中的一件。

您的别墅可能已经消失,取而代之的是俱乐部酒店。 你的别墅可能还在。 该岛可能会沉没。 ...

谁知道? 即使别墅可能不再属于您,您甚至可以再次跳入游泳池或打网球。 旁边可能还有另一栋别墅,您可以在更大的游泳池中游泳,而不会分散您的注意力。

如果你再次回来,你无法保证你会发现什么,这与你的向量相同,它在我看过的实现中包含三个指针: (名称可能不同,但功能大体相同。)

begin 指向分配内存位置的开始(即 X) end 指向分配内存+1的末尾(即开始+4) last 指向容器中的最后一个元素 +1(即 begin+4)

通过调用 clear 容器可能会销毁所有元素并重置 last = begin;。函数 size() 很可能是 return last-begin;,因此您将观察到容器大小为 0。 尽管如此,begin 可能仍然有效并且可能仍然分配了内存(end 可能仍然是begin+4)。你甚至可以观察到你在 clear() 之前设置的值。

std::vector<int> a(4);
a[2] = 12;
cout << "a cap " << a.capacity() << ", ptr is " << a.data() << ", val 2 is " << a[2] << endl;
a.clear();
cout << "a cap " << a.capacity() << ", ptr is " << a.data() << ", val 2 is " << a[2] << endl;

打印:

上限 4,ptr 是 00746570,val 2 是 12 上限 4,ptr 为 00746570,val 2 为 12

为什么你没有发现任何错误?这是因为std::vector&lt;T&gt;::operator[] 不执行任何越界检查(与std::vector&lt;T&gt;::at() 不同)。 由于 C++ 不包含“段错误”,因此您的程序似乎可以正常运行。

注意:如果在调试模式下编译,MSVC 2012 operator[] 会执行边界检查。

欢迎来到未定义行为的土地!事情可能发生也可能不会发生。您甚至可能无法对单一情况有所了解。 您可以冒险并大胆地研究一下,但这可能不是生成可靠代码的方法。

【讨论】:

【参考方案5】:

operator[] 高效但有代价:它不执行边界检查。

有更安全有效的方式来访问向量,例如迭代器等。

如果你需要一个随机访问的向量(即不总是顺序的),要么在编写程序时非常小心,要么使用效率较低的at(),在相同的条件下会引发异常。

【讨论】:

可能更准确的说法是,许多实现在清除后不会释放 - 请参阅 0x499602D2 答案中的 cmets。【参考方案6】:

您可能会遇到段错误,但这并不确定,因为在之前调用的clear() 之后使用operator[] 访问超出范围的向量元素只是未定义的行为。从您的帖子来看,您似乎想尝试元素是否被破坏,因此您可以为此目的使用at public 函数:

函数自动检查 n 是否在 向量中的有效元素,如果它抛出一个 out_of_range 异常 不是(即,如果 n 大于或等于其大小)。这是在 与成员 operator[] 相比,它不检查边界。

另外,clear()之后:

与此容器相关的所有迭代器、指针和引用都是 无效。

http://www.cplusplus.com/reference/vector/vector/at/

【讨论】:

【参考方案7】:

尝试访问您用于构造函数的 4 之外的元素,您可能会遇到分段错误 cplusplus.com 的另一个想法:

清除内容

从向量中移除所有元素(被销毁),留下大小为 0 的容器。

不保证会发生重新分配,也不保证向量容量会因为调用此函数而改变。强制重新分配的典型替代方法是使用交换:

vector().swap(x); // 清除 x 重新分配

【讨论】:

【参考方案8】:

如果你使用

temp1.at(2) = 343.5;

而不是

temp1[2] = 343.5;

你会发现问题。推荐使用at()的功能,operator[]不检查边界。不知道 STL vector 的实现,就可以避免这个 bug。

顺便说一句,我在我的 Ubuntu (12.04) 中运行你的代码,结果就像你说的那样。但是,在Win7中,却报“Assertion Failed”。

嗯,这让我想起了字符串流的类型。如果定义句子

stringstream str;
str << "3456";

如果重用str,我被告知这样做

str.str("");
str.clear();

而不仅仅是使用句子

str.clear();

我在 Ubuntu 中尝试了resize(0),但结果毫无用处。

【讨论】:

我很好奇 - 有没有办法让 GCC 像 MSVC 一样工作并自动检测此类问题?我通常在 Windows 上编程,我发现它非常很有帮助,但我也在 Linux 下工作,我想使用相同的错误检查机制。【参考方案9】:

是的,这很正常。 clear() 不保证重新分配。尝试在clear() 之后使用resize()

【讨论】:

resize 也不保证重新分配,但它保证将元素重置为已知值。【参考方案10】:

到目前为止,对答案的一个重要补充:如果实例化向量的类提供了析构函数,则会在清除时调用它(以及在 resize(0) 上)。

试试这个:

struct C

    char* data;
    C()            data = strdup("hello"); 
    C(C const& c)  data = strdup(c.data); 
    ~C()           delete data; data = 0; ;
;
int main(int argc, char** argv)

    std::vector<C> v;
    v.push_back(C());
    puts(v[0].data);
    v.clear();
    char* data = v[0].data; // likely to survive
    puts(data);             // likely to crash
    return 0;

这个程序很可能会因分段错误而崩溃 - 但(很可能)不是在char* data = v[0].data;,而是在puts(data); 行(使用调试器查看)。

典型的向量实现使分配的内存保持不变,并在调用析构函数后保持原样(但是,不能保证 - 请记住,这是未定义的行为!)。最后一件事是将 C 实例的数据设置为 nullptr,虽然对于 C++/vector 而言无效,但内存仍然存在,因此可以(非法)访问它而不会出现分段错误。当在 puts 中取消引用 char* data 指针时会发生这种情况,因为它是 null...

【讨论】:

以上是关于为啥 vector::clear 不从向量中删除元素?的主要内容,如果未能解决你的问题,请参考以下文章

为啥这段代码可以正常工作? (vector.clear(), vector<vector<int>>)

在 Kivy 中,为啥 popup.dismiss() 不从内存中删除弹出窗口?

为啥数据不从视图传递到控制器

如何删除具有特定条件的向量中的所有元组?

为啥 `as` 方法会删除向量名称,有没有办法解决它?

为啥 UITableView 不从数组中填充