Insure++ can detect several STL usage errors on Unix systems: |
This problem occurs when there is an attempt to dereference an unbound iterator–an iterator that has not been tasked as an iterator over any container.
When this program is compiled with g++, the resulting executable produces a SEG_FAULT
, but there is no indication of what went wrong.
#include <vector> using namespace std; typedef vector<int> VI; int main() { VI::iterator iter; int x = *iter; // unbound iterator. return 0; } |
$ insure g++ -g -Zstl unbound_iterator.cc -o unbound_iterator [unbound_iterator.cc:9] **STL_ERROR** >> int x = *iter; // unbound iterator. Dereferencing unbound iterator. Iterator at: 0xbffff6c0 (stack) iter, declared at unbound_iterator.cc, 8 Stack trace where the error occurred: iterator<>::operator*() (interface) main() unbound_iterator.cc, 9 |
Before you attempt to use an iterator, you should be certain that all paths initialize or assign to it. In this example, there wasn't even a container to which iterator iter
could have been bound. The following code shows the correct STL usage:
#include <vector> using namespace std; typedef vector<int> VI; int main() { VI v(1); VI::iterator iter = v.begin(); // creates and binds the iterator int x = *iter; return 0; } |
Valid iterators can become invalided by various STL operations.
#include <vector> using namespace std; typedef vector<int> VI; int main() { VI v(10); VI::iterator iter1 = v.begin()+ 5; VI::iterator iter2 = iter1 + 3; v.erase(iter1); // both iterators are now invalid. int x = *iter2; // invalid iterator. return 0; } |
$ insure g++ -g -Zstl invalid_iterator.cc -o invalid_iterator $ ./invalid_iterator [invalid_iterator.cc:12] **STL_ERROR** >> int x = *iter2; // invalid iterator. Dereferencing invalid iterator. Iterator at: 0xbffff680 (stack) iter2, declared at invalid_iterator.cc, 10 Container at: 0xbffff6b0 (stack) v, declared at invalid_iterator.cc, 8 Stack trace where iterator was bound: main() invalid_iterator.cc, 10 Stack trace where iterator was invalidated: vector<>::erase(iterator) (interface) main() invalid_iterator.cc, 11 Stack trace where the error occurred: iterator<>::operator*() (interface) |
Be aware of what STL operations invalidate the iterators of the container. For example, a vector erase() operation not only invalidates any iterators pointing to the erased element, but also, it invalidates all iterators from that point to the end of the vector. Be certain that you understand these rules, (which are different for each container type), and follow them.
One way of correcting this invalid program is to re-task the iterator before using it:
v.erase(iter1); // both iterators are now invalid iter2 = v.begin(); // iter2 is now retasked and is now valid int x = *iter2; // valid dereference. |
Although STLport and G++ can find some STL errors in debugging mode, the capabilities of Insure++ go beyond that. For example, Insure++ can catch uses of invalidated pointers and references, too:
#include <vector> using namespace std; typedef vector<int> VI; int main() { VI v(10); VI::iterator iter1 = v.begin()+ 5; int* p = &v[8]; v.erase(iter1); // iter1 and pointer "p" are now invalid. int x = *p; // READ_DANGLING (invalid pointer). return 0; } |
In the example shown above, the memory pointed to by p
might still be physically present not deleted), however, it is logically no longer part of the vector. Therefore, it is an error to dereference any pointer or reference to it.
$ insure g++ -g -Zstl invalid_pointer.cc -o invalid_pointer $ ./invalid_pointer [invalid_pointer.cc:12] **READ_DANGLING** >> int x = *p; // READ_DANGLING (invalid pointer). Reading from a dangling pointer: p Pointer : 0x08050070 In block: 0x08050064 thru 0x08050077 (20 bytes) stack trace where memory was destroyed: vector<>::erase(iterator) (interface) main() invalid_pointer.cc, 11 this block was internal to STL container: v, declared at invalid_pointer.cc, 8 Stack trace where the error occurred: main() invalid_pointer.cc, 12 |
Repair of this problem is similar to the repair of Problem 1. The vector
erase()
operation not only invalidates the memory at the erased element, but also, it invalidates the memory at all iterators, references and pointers from the erased element to the end of the vector.
v.erase(iter1); // iter1 and pointer "p" are now invalid p = &v[0]; // p is now retasked and is now valid int x = *p; // valid dereference. |
Insure++ also checks iterator boundary conditions.
#include <vector> using namespace std; typedef vector<int> VI; int main() { VI v; v.push_back(42); // v now contains one element. VI::iterator it = v.begin(); for (size_t count = 0; count <= v.size(); ++count, ++it) { int x = *it; // deferencing iterator at end of container. } return 0; } |
$ insure g++ -g -Zstl at_end.cc -o at_end $ ./at_end [at_end.cc:13] **STL_ERROR** >> int x = *it; Dereferencing iterator at end of container. Iterator at: 0xbffff6b0 (stack) it, declared at at_end.cc, 10 Container at: 0xbffff6c0 (stack) v, declared at at_end.cc, 8 Stack trace where iterator was bound: main() at_end.cc, 10 Stack trace where the error occurred: iterator<>::operator*() (interface) main() at_end.cc, 13 |
Because the loop termination condition is incorrect, it should be <
, not <=
, the iterator it
eventually moves to the end of the vector. It is illegal to dereference an iterator once it has reached the end of the container.
The for-loop should have been written as follows:
for (size_t count = 0; count < v.size(); ++count) {
Sometimes a valid iterator is not the only requirement. Certain STL operations require that the iterator not only be valid, but bound to the correct container for the given operation.
#include <vector> using namespace std; typedef vector<int> VI; int main() { VI v1(10); VI v2; VI::iterator iter = v1.begin()+ 5; v2.insert(iter, v1.begin(), v1.end()); // wrong container // ^^^ should be an iterator into v2, not v1. return 0; } |
$ insure g++ -g -Zstl wrong_containers.cc -o wrong_containers $ ./wrong_containers [wrong_containers.cc:12] **STL_ERROR** >> v2.insert(iter, v1.begin(), v1.end()); // wrong container Iterator bound to wrong container. Iterator at: 0xbffff610 (stack) [argument 1], declared in dbg_vector.h Container at: 0xbffff6b0 (stack) v1, declared at wrong_containers.cc, 8 Stack trace where iterator was bound: main() wrong_containers.cc, 12 Container the iterator should have been bound to: Container at: 0xbffff690 (stack) v2, declared at wrong_containers.cc, 9 Stack trace where the error occurred: vector<>::insert(iterator, InputIterator, InputIterator) (interface) main() wrong_containers.cc, 12 |
Be aware of the correct semantics for all STL operations. In this example, the user was trying to insert into vector v2
using an iterator that was bound to vector v1
. The repair is straight-forward:
v2.insert(v2.begin(), v1.begin(), v1.end());
This operation has the effect of inserting a copy of all elements of vector v1
into vector v2
at the start of vector v2
.
Some operations require an iterator range. A valid iterator range must have two valid iterators bound to the same container.
#include <vector> using namespace std; typedef vector<int> VI; int main() { VI v1(10); VI v2(5); VI::iterator iter1 = v1.begin(); VI::iterator iter2 = v2.begin(); v2.insert(v2.begin(), iter1, iter2); // different containers. return 0; } |
$ insure g++ -g -Zstl different_containers.cc -o different_containers $ ./different_containers [different_containers.cc:13] **STL_ERROR** >> v2.insert(v2.begin(), iter1, iter2); // different containers. Iterators refer to different containers. First iterator info: Iterator at: 0xbffff630 (stack) [argument 2], declared in dbg_vector.h Container at: 0xbffff6a0 (stack) v1, declared at different_containers.cc, 8 Stack trace where iterator was bound: main() different_containers.cc, 13 Second iterator info: Iterator at: 0xbffff640 (stack) [argument 3], declared in dbg_vector.h Container at: 0xbffff680 (stack) v2, declared at different_containers.cc, 9 Stack trace where iterator was bound: main() different_containers.cc, 13 Stack trace where the error occurred: vector<>::insert(iterator, InputIterator, InputIterator) (interface) main() different_containers.cc, 13 |
Be aware of the implicit and explicit rules for proper STL usage. In this example, iter2
should have been bound to vector v1
, not vector v2
. The repair is straight-forward:
VI::iterator iter2 = v1.end();
A valid iterator range must be properly ordered.
#include <vector> using namespace std; typedef vector<int> VI; int main() { VI v(10); VI v2(v.end(), v.begin()); // invalid iterator range return 0; } |
$ insure g++ -g -Zstl invalid_range.cc -o invalid_range $ ./invalid_range [invalid_range.cc:9] **STL_ERROR** >> VI v2(v.end(), v.begin()); // invalid iterator range Iterators do not constitute valid range. First iterator info: Iterator at: 0xbffff650 (stack) Container at: 0xbffff6b0 (stack) v, declared at invalid_range.cc, 8 Stack trace where iterator was bound: main() invalid_range.cc, 9 Second iterator info: Iterator at: 0xbffff670 (stack) Container at: 0xbffff6b0 (stack) v, declared at invalid_range.cc, 8 Stack trace where iterator was bound: main() invalid_range.cc, 9 Stack trace where the error occurred: main() invalid_range.cc, 9 |
A valid range of iterators has a starting point logically "less than" its ending point. In this case, the range is invalid because the order of arguments in the constructor were swapped. The correction is straight-forward:
VI v2(v.begin(), v.end());