smart vector - why?

Started by
13 comments, last by MaulingMonkey 15 years, 4 months ago
#include <vector>
#include <iostream>
using namespace std;

void main()
{
	vector<int> vec;
	vec.push_back(33);//0
	vec.push_back(66);//1
	vec.pop_back();
	cout<< vec[1];
}
Output: 66. So how does it remember there used to be a value "66" in that place? I thought popback erases the last value and then any RANDOM value would take its place. But it appears the vector remembers:)...
Advertisement
The reason is simple - undefined behaviour.

Undefined can include a simple crash, subtle memory corruption or even appearing to work correctly (and more). Or lovely combinations of the above, that change when you attach a debugger. Have fun [grin]
That should cause a runtime error, it does when I try your code on my machine in VS2005. On the other hand, why would it put a random value in place of the old one? It just frees the memory as far as I know.
inherently interactive - my game design blog
It probably doesn't even free memory; when you 'pop_back' it could simply decrease a pointer to the 'last' element in the memory it has reserved. That way when you push something new in it doesn't resize and just overwrites what is there.
@ Tim: Well this code
vector<int> a(3);cout<<a[10];
spits a random number so I figured the 1st example would, too.
Why isn't this throwing an out of bounds exception?
Quote:Original post by Tim Ingham-Dempster
That should cause a runtime error....

No, as rip-off mentioned, it's undefined behavior. It may cause a runtime error, but it can do anything include transform your computer into a beautiful lead crystal figurine. If you want an out of bounds exception, use at() rather than operator[].
Quote:Original post by SiCranetransform your computer into a beautiful lead crystal figurine.


I hear about that a lot, but has it actually ever happened to anyone?
Quote:Original post by Rattenhirn

I hear about that a lot, but has it actually ever happened to anyone?


Don't ask....

Quote:So how does it remember there used to be a value "66" in that place?


Because it never "forgot".

Although there's lots of talk about undefined behavior, it should be noted that in this particular case (considering actual implementation of std::vector, it isn't).

Vector is very conservative when it comes to releasing the memory. When it needs more, it usually allocates 2x the old size.

Now look at what can happen in this case (it's implementation dependant):
// X is undefined memory contentsHeap: [X][X][X][X][X][X][X][X][X][X][X]vector<int> vec;// Y is memory claimed by vecHeap: [Y][X][X][X][X][X][X][X][X][X][X]vec.push_back(33);//0Heap: [33][X][X][X][X][X][X][X][X][X][X]vec.push_back(66);// oops, out of room, need to resize// first, allocate 2x old size// Y is allocated memory, but yet initializedHeap: [33][Y][Y][X][X][X][X][X][X][X][X]// next, copy old contents into new memoryHeap: [33][33][Y][X][X][X][X][X][X][X][X]// release old memoryHeap: [X][33][Y][X][X][X][X][X][X][X][X]// next, push 66Heap: [X][33][66][X][X][X][X][X][X][X][X]vec.pop_back();// Vector hasn't re-allocated memory// so the old heap contents are still valid// and *defined*Heap: [X][33][66][X][X][X][X][X][X][X][X]          0   1


Given actual implementation of std::vector, this is not undefined behavior from C++ perspective. vec[1] is valid memory location.

Conceptually, this is the same as saying:
int a, b, c;bool allow_use_of_b;if (allow_use_of_b) cout << b;
In above case, we may choose to say that b isn't valid, but from language perspective, b doesn't change, and remains valid.

At this point it should be pointed out that many a topics have been started about "ZOMG STL IS SLOOOW!!!!!". The first thing everyone does at that point is turn off checked iterators.

Duh!

The whole reason that performance hog exists is to check for this type of scenario. Checked iterators and secure SCL exist not to prevent undefined behavior, but to check for undesired and almost certainly invalid *defined* behavior.

Note that this type of problem can trivially occur even in managed languages. This is purely a logic error with regard to implementation of std::vector, where subscript operator does not check bounds - managed languages do (ZOMG C# IS SLOW, why doesn't CLR remove redundant bounds checks!!!!).

But it is not a buffer overrun, nor undefined behavior (sometimes, but in case of vector rarely, can be).

In managed languages, people often implement a list, but make a small mistake.
void List::remove(int i) {  size--;  contents = contents[size];};
All fine and dandy. But what happens if List manages objects? contents[size] still references the object and remains alive. In managed languages, this would be a memory leak.

So from functional perspective, this type of behavior is closer to memory leak in managed languages than it is to buffer overrun.

See here (although article is mostly advertisement for a certain product, it was this type of problem).

The solution to above is obviously:
void List::remove(int i) {  size--;  contents = contents[size];  contents[size] = null;};
In case of OP, there is no 'null' value that could be written, and leaving original value, or writing a random one wouldn't matter.
Quote:Original post by Antheus
Although there's lots of talk about undefined behavior, it should be noted that in this particular case (considering actual implementation of std::vector, it isn't).


No, this is undefined. The operational semantics of v[n] on a vector v is *(v.begin() + n). If n is greater than or equal to the size of v, then you're dereferencing an iterator at end() or past end(). This is undefined behavior. See sections 23 and 24 in the C++ Standard, in particular section 23.1.1 paragraph 12.

This topic is closed to new replies.

Advertisement