Bezpieczne iteratory

Od wersji 2005 Visual C++ posiada opcje sprawdzania ważności iteratorów.

Nie będę tu produkował tekstu opisującego jak to dokładnie działa, na stronach MSDN można znaleźć kilka artykułów na ten temat: Checked Iterators, Debug Iterator Support.

W wersji debug aplikacji, program poczęstuje nas assertami, kiedy spróbujemy wykonać jakąś operacje na iteratorze bądź funkcji go wykorzystującej, w sytuacji kiedy strącił on ważność, np. przez doprowadzenie do relokacji danych w vectorze.

Checked iterators sprawdza również zakresy, wiec dereferencja na unieważnionym iteratorze zostanie wyłapana w czasie działania programu.

Do kontroli zachowania służą makra _SECURE_SCL i _HAS_ITERATOR_DEBUGGING.

Domyślnie _SECURE_SCL jest także włączone w wersji release, przez co możemy stracić trochę na wydajności i szybkości.

Oczywiście definicja odpowiednie makra z wartością 0, wyłączamy niestandardowe zachowanie, ale wtedy mogą pojawić się problemy przy linkowaniu z innymi bibliotekami, skompilowanymi z przeciwnymi ustawieniami tych zachowań. Prawdopodobnie przez użycie innej wersji CRT.

Dało mi to znać o sobie przy kompilowaniu programu z wyłączonym sprawdzaniem, z linkowaniem dynamicznej biblioteki z domyślnymi ustawieniami. Nagle debuger łapał dziwny wyjątek w dziwnym miejscu kodu, który był trywialny i poprawny.

Z drugiej strony mechanizm ten pomógł znaleźć mi kilka możliwych błędów na operacjach z iteratorami, których prawdopodobnie nigdy bym się nie spodziewał.

To fakt, że czasem robił małe zamieszanie. Na przykład próba porównania unieważnionego iteratora z innym była już niemożliwa w wersji debug, wykorzystując fakt, że vector trzyma dane w jednym, spójnym kawałku pamięci, mogłem łatwo sprawdzić czy dany iterator stracił ważność – wskazywał poza zakres.
A co by się stało jakby trafiał w dobry zakres? – wskazywałby na zupełnie inny element doprowadzając do niezdefiniowanego działania programu.

Innym uniedogodnieniem na jaki trafiłem, było pobranie adresu elementu na jaki wskazuje iterator. Próba dereferencji na ostatnim iteratorze (end) była sygnalizowana assertem z informacją o próbie dereferencji niepoprawnego iteratora.

Wskaźniki potrzebowałem tylko do traceowania i logowania przydatnych informacji w czasie debugowania i testowania. Oczywiście ktoś powie czemu nie wykorzystywałem indeksów w logach, powinny być bardziej czytelne i nie sprawiałyby tylu problemów. Ale jakoś wskaźniki w tym przypadku wydawały się lepsze, bo m.in. wykorzystywane są na zewnątrz poprzez API jako „uchwyty”.

Aby uniknąć problemu z dereferencja iteratora wskazującego na end, w celu pobrania tylko i wyłącznie wskaźnika do celów prezentacji liczbowej, musiałem nieco zasięgnąć bebechów implementacyjnych STL-a używanego przez VC w celu dostania się bezpośrednio do wartości wskaźnika przechowanego przez iterator.

Poniższa prosta „nakładka” pozwala na bezproblemowe pobranie wskaźnika do elementu, bez utraty przenośności:

template<typename T>
inline typename T::pointer Itr2Ptr(T iter) {
#if _MSC_VER >= 1400 && (_HAS_ITERATOR_DEBUGGING || _SECURE_SCL)
	return iter._Myptr;
#else
	return &*iter;
#endif
}

Mała uwaga.
Jakby ktoś zapomniał, iterator end nie wskazuje na ostatni element kontenera, tylko na następny element lub inną „umowną” wartość zależną od implementacji, więc dereferencja, pobranie wskaźnika i próba operowania na takim obiekcie jest skazana na niepowodzenie!

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *