32

When I use clear() on a std::vector, it is supposed to destroy all the elements in the vector, but instead it doesn't.

Sample code:

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;

Now, I should have gotten segmentation fault while trying to access the cleared vector, but instead it fills in the value there (which according to me is very buggy)

Result looks as follows:

4
0
Printing..... 343.5
0

Is this normal? This is a very hard bug to spot, which basically killed my code for months.

templatetypedef
  • 362,284
  • 104
  • 897
  • 1,065
Madhur Bhaiya
  • 28,155
  • 10
  • 49
  • 57
  • 2
    if you want to catch similar bugs, use a checked container (gcc can do this, or an external stl library, etc..) – Karoly Horvath Jun 24 '13 at 22:38
  • 1
    A segmentation fault is produced by a memory management unit, a hardware component that C++ does not require. If failing to get a segfault caused your program to misbehave, then you have more serious issues. – Potatoswatter Jun 24 '13 at 23:24
  • 7
    you can use `at` operator which will do bounds checking and give an exception. I recommend using `at` instead of `[]` – Bill Jun 25 '13 at 00:40
  • 1
    @KarolyHorvath: MSVC's libraries are checked in debug builds by default, and unchecked in release builds. It's awesome. – Mooing Duck Jun 25 '13 at 01:10
  • You might consider trying to analyze your code with the clang static analyzer: http://clang-analyzer.llvm.org/. I think it would flag this mistake. – Hack Saw Jun 25 '13 at 04:49
  • Other tools you may consider using - Purify, Valgrind etc - that will flag such memory access errors even if they may not result in a crash. – layman Jun 25 '13 at 06:52

10 Answers10

77

You have no right to get a segmentation fault. For that matter, a segmentation fault isn't even part of C++. Your program is removing all elements from the vector, and you're illegally accessing the container out of bounds. This is undefined behaviour, which means anything can happen. And indeed, something happened.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
31

When you access outside of the bounds of a vector, you get Undefined Behavior. That means anything can happen. Anything.

So you could get the old value, garbage, or a seg-fault. You can't depend on anything.

If you want bounds checking, use the at() member function instead of operator []. It will throw an exception instead of invoking Undefined Behavior.

zindorsky
  • 1,592
  • 9
  • 9
28

From cppreference:

void clear();

Removes all elements from the container. Invalidates any references, pointers, or iterators referring to contained elements. May invalidate any past-the-end iterators. Many implementations will not release allocated memory after a call to clear(), effectively leaving the capacity of the vector unchanged.

So the reason there is no apparent problem is because the vector still has the memory available in store. Of course this is merely implementation-specific, but not a bug. Besides, as the other answers point out, your program also does have Undefined Behavior for accessing the cleared contents in the first place, so technically anything can happen.

Community
  • 1
  • 1
David G
  • 94,763
  • 41
  • 167
  • 253
  • 1
    Is the highlighted part guaranteed by the standard, or is it rather just typical behavior? – Mark Ransom Jun 24 '13 at 22:37
  • But accessing out of bounds with `operator []` is still undefined behavior, so the the caller can't depend on getting the old value. Different compiler settings might, for example, write sentry values into the old elements. – zindorsky Jun 24 '13 at 22:41
  • 1
    @sasha.sochka Why does that matter? – Praetorian Jun 24 '13 at 22:42
  • All iterators, pointers and references related to this container are [invalidated.](http://www.cplusplus.com/reference/vector/vector/clear/) – 4pie0 Jun 24 '13 at 22:44
  • 1
    @MarkRansom The Standard just says it will clear the container of all its elements. I can't find anything (so far) which guarantees this behavior. – David G Jun 24 '13 at 22:48
  • 1
    @0x499602D2, I only ask because cppreference.com is a wiki and it can contain inaccurate information. – Mark Ransom Jun 24 '13 at 22:51
  • 2
    @0x499602D2 Sequence container requirements (§23.2.3/Table 100) states *a.clear() - Destroys all elements in a. Invalidates all references, pointers, and iterators referring to the elements of a and may invalidate the past-the-end iterator.* cppreference is wrong about both the `capacity()` being unchanged, and past-the-end iterators remaining valid. – Praetorian Jun 24 '13 at 22:52
  • 1
    @Praetorian I read that and I was confused by how cppreference would say the exact opposite. I will delete my answer if it needed. – David G Jun 24 '13 at 23:00
  • 1
    Yeah, I think this is misleading. See http://stackoverflow.com/questions/6882799/does-clearing-a-vector-affect-its-capacity -- I've updated cppreference to reflect that this is implementation-specific. – Nate Kohl Jun 24 '13 at 23:09
  • 1
    @NateKohl Why does the edit still say "Any past-the-end iterators are not invalidated.", the Standard says that they *may* be invalidated. – David G Jun 24 '13 at 23:21
8

Let's imagine you're rich (perhaps you are or you aren't ... whatsoever)!

Since you're rich you buy a piece of land on Moorea (Windward Islands, French Polynesia). You're very certain it is a nice property so you build a villa on that island and you live there. Your villa has a pool, a tennis court, a big garage and even more nice stuff.

After some time you leave Moorea since you think it's getting really boring. A lot of sports but few people. You sell your land and villa and decide to move somewhere else.

If you come back some time later you may encounter a lot of different things but you cannot be certain about even one of them.

  • Your villa may be gone, replaced by a club hotel.
  • Your villa may be still there.
  • The island may be sunken.
  • ...

Who knows? Eventhough the villa may not longer belong to you, you might even be able to jump in the pool or play tennis again. There may also be another villa next to it where you can swim in an even bigger pool with nobody distracting you.

You have no guarantee of what you're gong to discover if you come back again and that's the same with your vector which contains three pointers in the implementations I've looked at: (The names may be different but the function is mostly the same.)

  • begin points to the start of the allocated memory location (i.e. X)
  • end which points to the end of the allocated memory +1 (i.e. begin+4)
  • last which points to the last element in the container +1 (i.e. begin+4)

By calling clear the container may well destroy all elements and reset last = begin;. The function size() will most likely return last-begin; and so you'll observe a container size of 0. Nevertheless, begin may still be valid and there may still be memory allocated (end may still be begin+4). You can even still observe values you set before 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;

Prints:

a cap 4, ptr is 00746570, val 2 is 12
a cap 4, ptr is 00746570, val 2 is 12

Why don't you observe any errors? It is because std::vector<T>::operator[] does not perform any out-of-boundary checks (in contrast to std::vector<T>::at() which does). Since C++ doesn't contain "segfaults" your program seems to operate properly.

Note: On MSVC 2012 operator[] performs boundary checks if compiled in the debug mode.

Welcome to the land of undefined behaviour! Things may or may not happen. You probably can't even be cartain about a single circumstance. You can take a risk and be bold enough to take a look into it but that is probably not the way to produce reliable code.

Pixelchemist
  • 24,090
  • 7
  • 47
  • 71
6

The operator[] is efficient but comes at a price: it does not perform boundary checking.

There are safer yet efficient way to access a vector, like iterators and so on.

If you need a vector for random access (i.e. not always sequential), either be very careful on how you write your programs, or use the less efficient at(), which in the same conditions would have thrown an exception.

Antonio
  • 19,451
  • 13
  • 99
  • 197
  • It's probably more accurate to say that many implementations don't deallocate after a clear -- see the comments on 0x499602D2's answer. – Nate Kohl Jun 24 '13 at 23:13
2

you can get seg fault but this is not for sure since accessing out of range elements of vector with operator[] after clear() called before is just undefined behavior. From your post it looks like you want to try if elements are destroyed so you can use at public function for this purpose:

The function automatically checks whether n is within the bounds of valid elements in the vector, throwing an out_of_range exception if it is not (i.e., if n is greater or equal than its size). This is in contrast with member operator[], that does not check against bounds.

in addition, after clear():

All iterators, pointers and references related to this container are invalidated.

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

4pie0
  • 29,204
  • 9
  • 82
  • 118
2

If you use

temp1.at(2) = 343.5;

instead of

temp1[2] = 343.5;

you would find the problem. It is recommended to use the function of at(), and the operator[] doesn't check the boundary. You can avoid the bug without knowing the implementation of STL vector.

BTW, i run your code in my Ubuntu (12.04), it turns out like what you say. However, in Win7, it's reported "Assertion Failed".

Well, that reminds me of the type of stringstream. If define the sentence

stringstream str;
str << "3456";

If REUSE str, I was told to do like this

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

instead of just using the sentence

str.clear();

And I tried the resize(0) in Ubuntu, it turns out useless.

Regexident
  • 29,441
  • 10
  • 93
  • 100
fibonacci
  • 2,254
  • 2
  • 17
  • 13
  • I'm curious - is there a way to make GCC work like MSVC and detect such issues automatically? I usually program on Windows and I find it **very** helpful, but I also work under Linux and I'd like to use the same error checking mechanism. – Andrei Bârsan Jun 25 '13 at 11:36
2

try to access to an elements sup than 4 that you use for constructor may be you will get your segmentation fault An other idea from cplusplus.com:

Clear content

Removes all elements from the vector (which are destroyed), leaving the container with a size of 0.

A reallocation is not guaranteed to happen, and the vector capacity is not guaranteed to change due to calling this function. A typical alternative that forces a reallocation is to use swap:

vector().swap(x); // clear x reallocating

beyrem
  • 437
  • 1
  • 3
  • 12
1

Yes this is normal. clear() doesn't guarantee a reallocation. Try using resize() after clear().

kendotwill
  • 1,892
  • 18
  • 17
  • 1
    `resize` doesn't guarantee a reallocation either, but it does guarantee that the elements will be reset to known values. – Mark Ransom Jun 25 '13 at 03:19
0

One important addition to the answers so far: If the class the vector is instanciated with provides a destructor, it will be called on clearing (and on resize(0), too).

Try this:

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;
}

This program most likely will crash with a segmentation fault - but (very likely) not at char* data = v[0].data;, but at the line puts(data); (use a debugger to see).

Typical vector implementations leave the memory allocated intact and leave it as is just after calling the destructors (however, no guarantee - remember, it is undefined behaviour!). Last thing that was done was setting data of the C instance to nullptr, and although not valid in sence of C++/vector, the memory is still there, so can access it (illegally) without segmentation fault. This will occur when dereferencing char* data pointer in puts, as being null...

Aconcagua
  • 24,880
  • 4
  • 34
  • 59