为啥 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<T>::operator[]
不执行任何越界检查(与std::vector<T>::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>>)