views:

114

answers:

4

vector<vector<string> > test for example.
g++, you can do

test.reserve(10);
test[0] = othervector;
test[9] = othervector;

It doesn't crash. Theory says you shouldn't do it like that because you are assigning a vector to a chunk of memory that believes it is a vector.
But it works just like the next one:

#include <string>
#include <vector>
#include <iostream>
using namespace std;
int main(){
    vector<string> first_vector;
    vector<string> &second_vector = *(vector<string>*)new char[sizeof(vector<string>)];
    first_vector.push_back("whatever");
    first_vector.push_back("whatever2");
    first_vector.push_back("whatever3");
    second_vector = first_vector;
    cout << "0 " << second_vector[0] << " \n";
    cout << "1 " << second_vector[1] << " \n";
    cout << "2 " << second_vector[2] << " \n";
}

This looks to me like the assignment operator of a vector actually copies all, or at least enough fields of the vector implementation for this to work, rendering a perfectly valid vector in the uninitialized.

Well, this is clearly undefined behavior to me, only problem is that it works as expected, I found quite a lot of these in a codebase I'm currently inspecting.

Are there more cases like this one in the rest of the containers? Never seen one that looks so easy to make a mistake but that it actually works even if you make the mistake.

EDIT: This is not about how to do the above properly or complain about the compilers behavior, its trying to find similar issues easy to happen and really difficult to spot later, like this one.

+1  A: 

"Undefined behaviour" is just that: undefined. You have found a situation on one compiler, under one OS, where the behaviour is what you expect. This is actually quite common - compilers don't take "undefined behaviour" to mean "automatically segfault"! But the problem with undefined behaviour is that because the Standard doesn't guarantee it, if you later change OS, or compiler, or upgrade to a later version of the same compiler, you can't rely on the behaviour remaining the same. It may well blow up in your face.

Philip Potter
Never said that it should blow. I was actually asking for similar situations when an undefined behavior can be seen as defined like this one. reserve/resize, both work apparently, but reserve is "undefined behavior".
Arkaitz Jimenez
I did read your whole post. I don't think it's a useful question to ask, because when UB works as expected, that might be because of a particular coincidence between compiler, OS, hardware, library, and code. It's hard to determine the exact circumstances under which behaviour is repeatable. There's also the problem of defining what "expected behaviour" is when you invoke UB. In your original example, if you call `test.push_back`, you'll probably overwrite test[0], so `resize` isn't totally equivalent to `reserve`.
Philip Potter
good point on push_back.
Arkaitz Jimenez
@Arkaitz: Using reserve is not UB; using op[] with an invalid index is UB. Reserve affects capacity, not size. Resize affects both (because it changes size and capacity must be >= size). Size determines which indices are valid. **UB is never seen as defined, by definition.**
Roger Pate
A: 

You should use vector::resize instead of vector::reserve.

I also made this error once and it worked without problems on 32-bit Windows, but crashed when everything was compiled as 64-bit.

That's what 'undefined' means: it may work, or it may suddenly stop working. No guarantee (not even the guarantee that it crashes).

Patrick
I'm not asking what to use, I'm asking about similar situations, do u guys read the whole posts?
Arkaitz Jimenez
+2  A: 

So you are asking for arbitrary misuses of the STL?

I think a rather typical one that can make beginners suspect a bug in the library is trying to store objects that don't implement copying properly in a container. I guess that may or may not appear to work.

Another common error is not taking into account iterator invalidation (e.g when inserting/erasing while looping).

With invalid casts one can probably create all kinds of scenarios.

UncleBens
The copy construction and proper assignment are a good point of failure for beginners. Casting here and there is rather an obvious point to look for a mistake.
Arkaitz Jimenez
+2  A: 

"Are there more cases like this one in the rest of the containers?"

Tons. And I'm not kidding. I can come up with literally thousands of them, all over the standard.

For instance, almost all functions that take an iterator range can break in nasty ways if you pass in two unrelated iterators. They might also "work" silently but wrongly, in particular if you pass in two vector iterators.

Similarly, in all implementations I know of, for any type T reinterpret_cast<T&>(&random_bytes).operator=(T()); may work for some values of random_bytes. It's still UB.

MSalters
Well, iterators and casts are suspects for me as well, so i don't consider them easy to overlook errors.
Arkaitz Jimenez