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

时间:2013-06-24 22:33:52

标签: c++ stdvector undefined-behavior

当我在clear()上使用std::vector时,它应该销毁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

这是正常的吗?这是一个非常难以发现的错误,基本上几个月来我的代码被杀了。

10 个答案:

答案 0 :(得分:76)

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

答案 1 :(得分:28)

当您在向量的边界之外访问时,您将获得未定义的行为。这意味着一切都会发生。任何东西。

所以你可以获得旧的值,垃圾或seg-fault。你不能依赖任何东西。

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

答案 2 :(得分:26)

来自cppreference:

void clear();
     

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

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

答案 3 :(得分:7)

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

由于你很富有,你在Moorea(法属波利尼西亚向风群岛)购买了一片土地。 你非常肯定这是一个不错的财产,所以你在那个岛上建造一个别墅,你住在那里。 你的别墅有游泳池,网球场,大车库和更好的东西。

过了一段时间,你离开了Moorea,因为你认为它变得非常无聊。很多运动但人很少。 你出售你的土地和别墅,并决定搬到其他地方。

如果你以后再回来,你可能会遇到很多不同的事情,但你甚至不能确定其中一个。

  • 您的别墅可能已经不在了,取而代之的是俱乐部酒店。
  • 你的别墅可能还在那里。
  • 岛上可能是沉没的。
  • ...
谁知道? 虽然别墅可能不再属于你,但你甚至可以跳进游泳池或再次打网球。 旁边还有另一栋别墅,您可以在更大的游泳池游泳,没有人分散您的注意力。

如果你再次回来,你无法保证发现你会发现什么,而且你的矢量包含了我所看到的实现中的三个指针: (名称可能不同,但功能大致相同。)

  • begin指向分配的内存位置的开头(即X)
  • end指向分配的内存+1的结尾(即begin + 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[]执行边界检查,如果在调试模式下编译。

欢迎来到未定义行为的土地!事情可能会也可能不会发生。你可能甚至无法对单一情况采取任何措施。 您可以冒险并大胆地查看它,但这可能不是生成可靠代码的方法。

答案 4 :(得分:6)

operator[]效率很高,但需要付出代价:它不会执行边界检查。

访问矢量有更安全有效的方法,比如迭代器等。

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

答案 5 :(得分:2)

你可以得到seg错误,但这不是肯定的,因为在operator[]调用之前访问带有clear()的向量的范围元素只是未定义的行为。从您的帖子看起来您想要尝试元素是否被销毁,以便您可以使用at公共函数来实现此目的:

  

该函数自动检查n是否在范围内   向量中的有效元素,如果它,则抛出out_of_range异常   不是(即,如果n大于或等于其大小)。这是在   与成员运算符[]形成对比,不检查边界。

此外,在clear()之后:

  

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

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

答案 6 :(得分:2)

如果您使用

temp1.at(2) = 343.5;

而不是

temp1[2] = 343.5;
你会发现问题所在。建议使用at()的功能,而operator[]不检查边界。您可以在不知道STL向量的实现的情况下避免该错误。

顺便说一下,我在 Ubuntu(12.04)中运行你的代码,结果就像你说的那样。但是,在 Win7 中,报告“断言失败”。

嗯,这让我想起了stringstream的类型。如果定义句子

stringstream str;
str << "3456";

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

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

而不只是使用句子

str.clear();

我尝试了 Ubuntu 中的resize(0),结果证明没用。

答案 7 :(得分:2)

尝试访问用于构造函数的 4 元素可能会导致您的分段错误 来自cplusplus.com的另一个想法:

清除内容

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

无法保证重新分配,并且由于调用此函数,无法保证向量容量发生变化。强制重新分配的典型替代方法是使用swap:

<强>矢量()交换(X)。 //清除x重新分配

答案 8 :(得分:1)

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

答案 9 :(得分:0)

到目前为止,答案的一个重要补充:如果向量实例化的类提供了析构函数,它将在清除时调用(并且也在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 ...