83. Используйте отладочную реализацию STL

We use cookies. Read the Privacy and Cookie Policy

83. Используйте отладочную реализацию STL

Резюме

Безопасность превыше всего (см. рекомендацию 6). Используйте отладочную реализацию STL[4], даже если она имеется только для одного из ваших компиляторов, и даже если она используется только для отладочного тестирования.

Обсуждение

Так же, как легко ошибиться при работе с указателями, легко допустить и ошибку при работе с итераторами, причем такую, что программа при этом успешно компилируется, а при работе немедленно аварийно завершается (в лучшем случае) или работает неверно (в худшем). Даже если ваш компилятор не обнаружил ни одной ошибки, вы не должны полагаться на "визуальный контроль": имеются специальные инструменты — воспользуйтесь ими.

Ряд ошибок при работе с STL часто допускают даже опытные программисты.

• Использование недействительных или неинициализированных итераторов. В особенности легко допустить первое.

• Передача индекса, выходящего за границы контейнера. Например, обращение к 113-му элементу 100-элементного контейнера.

• Использование "диапазона" итераторов, который на самом деле не является диапазоном. Например, передача двух итераторов, первый из которых не предшествует второму, или когда они указывают на разные контейнеры.

• Передача неверной позиции итератора. Вызов функции-члена контейнера, которая получает позицию итератора (как, например, позиция, передаваемая функции insert), но с передачей итератора, который указывает в другой контейнер.

• Использование неверного упорядочения. Предоставление неверного правила для упорядочения ассоциативного контейнера или в качестве критерия сравнения в алгоритме сортировки (см. примеры в [Meyers01]). Если не использовать отладочной версии STL, такие ошибки обычно проявляются в процессе выполнения программы как неверное поведение или бесконечные циклы, но не как аварийный останов.

Большинство отладочных реализаций STL автоматически обнаруживают такие ошибки, при помощи дополнительной отладочной и системной информации, хранящейся в контейнерах и итераторах. Например, итератор может запомнить контейнер, к которому он обращается, а контейнер — запомнить все итераторы, ссылающиеся на него, так что можно отследить использование ставшего недействительным итератора. Конечно, это увеличивает размер итераторов и контейнеров, а также требует выполнения дополнительных действий при каждом изменении контейнера. Но возможность отслеживания ошибок стоит того — по крайней мере, в процессе отладки, а возможно, и в окончательной версии программы (вспомните рекомендацию 8; не отключайте полезные проверки для повышения производительности до тех пор, пока не убедитесь в необходимости такого шага непосредственными измерениями).

Даже если вы не намерены оставлять проверки в окончательной версии, и даже если у вас имеется проверочная реализация STL только для одной из используемых вами платформ, обязательно протестируйте вашу программу полным комплектом тестов с использованием отладочной реализации STL. Вы не пожалеете об этом.

Примеры

Пример 1. Использование недействительного итератора. Очень просто забыть, что итераторы стали недействительны, и воспользоваться таким недействительным итератором (см. рекомендацию 99). Рассмотрим подобный пример, адаптированный из [Meyers01], где происходит вставка элементов в начало deque:

deque<double>::iterator current = d.begin();

for (size_t i =0; i < max; ++i )

 d.insert(current++, data[i] + 41); // Видите ли вы ошибку?

Быстро ответьте — увидели ли вы ошибку в приведенном фрагменте или нет? У вас только три секунды!

Время вышло! Если вы не заметили ошибку за отпущенное время, не волнуйтесь — это достаточно тонкая и понятная ошибка. Отладочная версия STL обнаружит данную ошибку на второй итерации цикла, так что вам не придется полагаться на свой невооруженный взгляд. (Исправленная версия данного кода и альтернативные варианты вы найдете в рекомендации 84.)

Пример 2. Использование диапазона итераторов, на самом деле не являющегося диапазоном. Диапазон итераторов представляет собой пару итераторов first и last, которые указывают на первый элемент диапазона, и элемент, следующий за последним элементом диапазона. Диапазон требует, чтобы итератор last был достижим из first путем некоторого количества повторных увеличений при помощи оператора инкремента итератора first. Известны два распространенных вида ошибки, когда происходит попытка использовать диапазон, который на самом деле диапазоном не является. Первая ошибка возникает, когда два итератора ограничивают диапазон в пределах одного контейнера, но итератор first на самом деле не предшествует итератору second:

for_each(c.end(),с.begin(),Something); // не всегда очевидно

На каждой итерации своего внутреннего цикла алгоритм for_each будет проверять итератор first на равенство итератору second, и до тех пор, пока они не равны, он будет увеличивать итератор first. Конечно, сколько бы раз не был увеличен итератор first, ему никогда не стать равным итератору second, так что цикл по сути оказывается бесконечным. На практике в лучшем случае цикл завершится выходом за пределы контейнера с и немедленным аварийным завершением программы из-за нарушения защиты памяти. В худшем случае, выйдя за пределы контейнера, итератор будет считывать или даже менять данные, не являющиеся частью контейнера. Этот результат в принципе не слишком отличается от знакомых нам последствий переполнения буфера…

Вторая распространенная ошибка возникает, когда итераторы указывают в разные контейнеры:

for_each(c.begin(),d.end(),Something); // не всегда очевидно

Результат такой ошибки аналогичен результату предыдущей. Однако поскольку итераторы отладочной версии STL помнят контейнер, с которым работают, такую ошибку можно выявить во время выполнения программы.

Ссылки

[Dinkumware-Safe] • [Horstmann95] • [Josuttis99] §5.11.1 • [Metrowerks] • [Meyers01] §21, §50 • [STLport-Debug] • [Stroustrup00] §18.3.1, §19.3.1