Средства тестирования от компании Rational
Rational Purify. Интеграция с Visual C++
Rational Purify. Некоторые тонкости
Тестирование сервисов Windows NT
Сообщения об ошибках и предупреждениях. Настройка фильтра
C развитием аппаратной базы компьютерных систем — увеличением тактовой частоты (до запредельного уровня, перевалившего за гигагерц), ускорением обработки изображений в реальном масштабе времени при помощи супермощных видеоадаптеров — многие разработчики не считают нужным (или возможным) оптимизировать написанные ими программные продукты, перенося весь неоптимизированный код на быструю подсистему, быстрый процессор, «умный» компилятор.
Результаты подобного поведения мы ежедневно наблюдаем во время запуска программ на собственных компьютерах — чем новее программа, тем больше она требует ресурсов и тем медленнее работает. Но и это еще не все: многие программы по окончании работы не освобождают все занимаемые ресурсы, что приводит к достаточно неприятным последствиям. Странно, не правда ли? Казалось бы, технологии программирования должны совершенствоваться и идти в ногу с аппаратными новинками, эффективно используя все предоставляемые ими возможности, однако на деле все обстоит иначе. В погоне за новыми номерами версий на коробках с программными продуктами разработчики нередко не считают нужным производить детальную оптимизацию написанного кода, тщательно отслеживать все вызовы и вычислять занимаемую оперативную память, поскольку занятие это трудоемкое и длительное, а получаемый результат не всегда оправдывает надежды: времени потрачено много, сил еще больше, а производительность конечного продукта повысилась в лучшем случае на 10%, а то и меньше. Согласитесь, ситуация для нашего времени достаточно типичная, причем типичная для всех софтверных компаний, независимо от их ранга, размера и географического положения. Принцип «Время — деньги» в данной ситуации явно не срабатывает! Количество новых версий идет в ущерб качеству. За примером далеко ходить не надо: у всех есть «нерасторопные» операционные системы и офисные пакеты, аппаратные требования для которых с каждой новой реализацией повышаются, а функциональность при этом остается на уровне 1995 года.
Неужели все так беспросветно и бесперспективно? И да и нет. Все крупные компании, связанные с написанием программного обеспечения, пытаются найти способы наиболее эффективного тестирования, не выходя за рамки бюджета конкретной разработки. Однако, к сожалению, то ли ищут не там, то ли применяют не то — но оптимизированных продуктов на рынке не так много.
Разработка качественных продуктов в четко определенные сроки — это задача, для решения которой предназначены все продукты компании Rational Software. В частности, для этапа разработки высококачественного кода посредством RUP (Rational Unified Process — методология разработки Rational) эта компания рекомендует использовать инструменты тестирования Quantify, Purify и PureCoverage. Продукт Rational Quantify собирает полную статистику о количестве вызовов функций в тестируемой программе, позволяя тем самым узнавать временные характеристики отдельных частей приложения. Rational Pure Coverage дает возможность быстро найти участки кода, пропущенные при тестировании. Rational Purify отлавливает все ошибки, связанные с утечкой памяти, а также некоторые ошибки времени выполнения.
Данная статья посвящена методике использования вышеперечисленных продуктов, ведь их разумное применение позволит разработчикам найти «узкие места» в производительности создаваемого программного обеспечения и своевременно устранить их.
Далее разговор пойдет о наиболее «продвинутом» продукте из вышеприведенного списка — Rational Purify. Этот продукт является самым сложным и самым «интеллектуальным» в силу своей специфики, поскольку в обязанности ему вменяется нахождение ошибок, а не простая статистическая выкладка, которую выдают остальные пакеты.
Rational Purify. Интеграция с Visual C++
Возможности продукта Rational Purify можно охарактеризовать так: «с точностью до миллибайта». И это не случайно, данный продукт предназначен именно для решения всех проблем, связанных с утечкой памяти.
Не секрет, что многие приложения ведут себя не слишком скромно, без особой на то необходимости «замыкая» на себя во время работы все системные ресурсы. Этой «болезни» подвержены в основном крупные программные пакеты. А возникает подобная ситуация вследствие нежелания программистов доводить созданный код до ума, причем чаще не из-за лени, а из-за невнимательности. Это вообще-то понятно: современные темпы разработки программного обеспечения в условиях жесточайшей конкуренции не позволяют уделять оптимизации кода слишком много времени, ведь для этого необходимы и высокая квалификация, и наличие достаточного количества времени. Как правило, квалификация имеется, а вот времени и денег на детальное тестирование кода на наличие «глюков» и «багов» практически нет. И в большинстве случаев, идя на поводу у клиентов, крупные компании выпускают сырой код и тут же принимаются «латать» его различными патчами…
Еще раз повторяю, что основная часть всех ошибок приходится на неправильное распределение/использование памяти, а также на несанкционированный доступ за границы собственного адресного пространства. Для программистов на С и С++ дополнительная головная боль — это указатели, предоставляющие собой одну брешь в системе.
Мне кажется, что разработчики, имея в своем распоряжении надежный инструмент, который сам в процессе работы над проектом указывал бы на подобные ошибки, начали бы его повсеместное внедрение, повысив надежность создаваемого программного обеспечения, поскольку, как известно, лучше не допустить ошибку, чем потом ее исправлять.
В общих чертах работа Rational Purify заключается в детальном выводе статистики использования памяти приложением (список ошибок и примеры их устранения будут приведены ниже). Выдаваемой статистики вполне достаточно для получения сначала общей, а затем и детальной информации обо всем, что имеет отношение к памяти: утечка, потерянные блоки, фиктивные ссылки.
Purify позволяет анализировать исполняемый модуль, содержащий отладочную информацию, и работать на уровне исходных текстов, но только в среде Microsoft Visual Studio. Работа этого продукта начинается со сбора информации о загружаемых библиотеках. Purify отыскивает не только внутренние, но и внешние ошибки. Разумеется, нельзя исправить ошибку в системной DLL, но если она там есть, то наверняка можно написать дополнительный код в своей процедуре, отказавшись от вызова «некачественной» функции из DLL.
Прежде всего идет детальное инструментирование всех модулей. Инструментирование сводится к тому, что Purify вставляет свою отладочную информацию в тело программы и вызываемых библиотек (используя технологию Object Code Insertion). Для эффективной работы все «пройденные» библиотеки хранятся в кэше, что позволяет сократить необходимое время на перезапуск исправленного приложения.
А теперь рассмотрим возможности Purify в плане совместной работы с Microsoft Visual C++.
Интеграция компонентов от Rational выражается в появлении новых инструментальных панелей на поверхности рабочего стола в Development Studio. Получив полный набор тестирующих и профилирующих средств, разработчик обращается к ним по мере необходимости, не покидая рабочего пространства, что позволяет сэкономить массу времени на различные вызовы сторонних программ.
На рис. 1 показан примерный вид инструментальных панелей в Visual Studio, появляющихся после инсталляции Purify, Quantify, PureCoverage.
Собрав воедино всю вышеизложенную информацию, попробуем на конкретном примере создать приложение в Visual C++, внести в него ряд намеренных ошибок, а затем, используя Purify, попытаться отыскать их.
Мы не будем останавливаться на том, каким образом создаются проекты в Visual Studio и из чего они состоят, поскольку исходим из того, что читатель уже работал с данной средой или хотя бы знаком с ней. Для чистоты эксперимента воспользуемся стандартным мастером из состава Visual Studio, сгенерировав на его основе проект, куда внесем некий код, который будет неадекватно себя вести с памятью операционной системы.
Итак, у нас получилось 32-разрядное приложение для Windows с именем PROJECTOID. На рис. 2 изображено окно Workspace после создания проекта. Чтобы продемонстрировать преимущества Purify, не нужно заводить в примере «тонны» сложных классов, запутывая и себя, и программу, и статью, — ограничимся лишь простыми вызовами на распределение памяти.
Для более наглядной демонстрации способа поиска ошибок допишем пару строк в стандартный обработчик OnAppAbout:
void All::OnAppAbout() { char *alex; //Наша строка №1 alex=(char *)malloc(20000); //Наша строка №2 CAboutDlg aboutDlg; aboutDlg.DoModal(); }
Добавление «интеллекта» к функции OnAppAbout сделано намеренно, поскольку во время работы можно воспользоваться данной функцией несколько раз подряд, активируя диалог ABOUT после «игр» с его вызовом. Теперь завершим приложение, посмотрим статистику по памяти и в итоге найдем «виновного» в полученной утечке памяти. Из фрагмента видно, что указатель alex указывает на блок длиной в 20 Кбайт, который выделяется функцией MALLOC. Заметить, что указатель нигде не используется и что блок памяти не освобождается.
Запускаем приложение на выполнение с помощью клавиши F5, предварительно активировав Purify («увеличительные стекла» на инструментальной панели Purify, см. рис. 1). В запущенном приложении трижды запускаем диалог ABOUT из главного меню, а затем закрываем приложение.
Во время работы подконтрольного приложения Purify собрал достаточно информации о нем и о его проблемах с памятью. Полученная статистика будет выведена на экран сразу по окончании работы приложения.
На рис. 3 изображен вид окна со статистикой использования памяти. При внимательном рассмотрении становится видна вся «подноготная» как нашего модуля, так и «шапки», сгенерированной компилятором Microsoft Visual C++. Purify насчитала 43 (!) предупреждения о неправильном (неэффективном) использовании памяти, а из них только одно было преднамеренно нами введено в программу. Хотя скажем честно: не все ошибки являются ошибками (об этом мы поговорим ниже).
Вновь обратимся к рисунку со статистикой, где в явном виде представлена информация об ошибках и о модулях, в которых эти ошибки были обнаружены. Нас приятно удивила надпись «Memory leak of 60000», указывающая на то, сколько фактических байт памяти программа не вернула операционной системе по завершении работы. Это выгодно отличает подходы к тестированию программы Rational Purify от подобных продуктов конкурирующих компаний, которые высчитывают не фактические утечки, полученные в результате нескольких вызовов при реальной работе приложения, а количество невозвращенных блоков, то есть ограничиваются лишь анализом (на уровне исходных текстов) программы с выявлением вызовов функций резервирования памяти без последующего освобождения. Полученное число 60 000 — это количество фактически не освобожденных блоков (3 по 20 000). После добавления функции free(alex) в конец обработчика OnAppAbout и перекомпиляции тестируемого приложения Purify не обнаруживает никаких ошибок памяти, что и являлось нашей целью.
Окно c ошибкой, в котором Purify выделила конкретный фрагмент текста листинга, изображено на рис. 4.
Все вышеописанные возможности позволяют разработчику узнать, где в коде находится вызов, приводящий к выделению памяти, сколько физически утеряно блоков в результате работы приложения и какая часть программы в этом виновата.
Rational Purify. Некоторые тонкости
Из вышесказанного читателям стало известно, что Rational Purify предназначен для поиска ошибок в программах, созданных с помощью Visual Studio. Этот продукт способен также проанализировать эффективность кода программы — как с использованием исходных текстов, так и без них. Однако достоинства Rational Purify этим не ограничиваются. Purify можно запустить из среды Visual Studio как отдельную программу и из командной строки (если это было предусмотрено при ее установке). Напрасно разработчик, создающий Windows-приложения, отказывается от интерфейса командной строки за ненадобностью. Как известно, добрая половина продуктов Rational уходит своими корнями в UNIX-системы, для которых командная строка — вещь святая, не заменяемая графическими средствами. Соответственно все особенности этих продуктов перекочевали в Windows, где и прижились — в лучшей или худшей степени. Обратим свое внимание именно на командную строку и попробуем разобраться, для чего она нужна в повседневной разработке.
Выше мы уже говорили о том, что при работе средства тестирования Rational используют патентованную (читай — засекреченную) технологию под названием OCI Object Code Insertion. Суть метода тестирования состоит в том, что в исполняемый код записываются специальные инструкции Purify. Необходимо еще раз подчеркнуть: код вставляется не только в пользовательский модуль, но и во все внешние библиотеки, что дает разработчику уникальную возможность отладки программ, предоставляет полную статистику по всем модулям. А это позволяет вовремя переписать некорректную DLL, а не ждать, пока она сведет на нет все усилия по выявлению внутренних ошибок. К вопросу о скорости работы: код вставляется довольно долго, зато создается директория с кэшем — DLL со вставленным OCI, так что каждый новый запуск проходит быстрее предыдущего.
Процесс записи объектного кода в приложения на языке Purify называется инструментированием, и подобная операция выполняется каждый раз перед исполнением написанного приложения. Если это касается совместной работы с пакетами из Visual Studio, то сначала приложение компилируется (обычным способом, без вставки OCI), затем, после вызова команды RUN, запускается Purify и начинается процесс вставки кода. Только после его завершения приложение начнет выполняться. Из самого же Purify инструментирование осуществляется еще проще: необходимо только выбрать нужный исполняемый файл. Естественно, в обоих случаях приложению (при необходимости) можно передать аргументы командной строки и настроить фильтры сообщений.
Полное же управление процессом инструментирования можно получить из командной строки. Здесь есть некоторые возможности, отсутствующие в GUI-версиях, например:
- инструментирование как самого файла приложения, так и его копии (последняя создается автоматически);
- только вставка кода без запуска приложения;
- запись кэша в указанную директорию;
- и многое другое (всего 38 ключей).
Тестирование сервисов Windows NT
Конечно, командная строка во многом дублирует функции графической оболочки, что делает не совсем очевидным применение именно данной возможности. Для решения следующего примера нам нужно воспользоваться командной строкой, поскольку тестируемое приложение является сервисом Windows NT, который, разумеется, нельзя исполнить как простое приложение. В этом случае как нельзя кстати придется функция записи OCI без исполнения файла, что позволит внести в него весь объектный код, а затем прописать данный сервис в реестре. Следовательно, при старте сервиса у вас появится возможность получения информации о работоспособности сервиса.
«Рецепт» тестирования выглядит следующим образом:
- Правильно настроить переменную окружения Path таким образом, чтобы были доступны все директории Purify (особенно кэш: \Program Files\Rational\Purify), иначе выполнение процесса не начнется.
- Откомпилированный сервис нужно запустить из командной строки следующим образом: purify /Run=no /Out=service_pure.exe service.exe. Как видно из параметров, Purify инструментирует файл service.exe, помещая его копию вместе с OCI в service_pure.exe. Все происходит без запуска.
- В ключе реестра \HKEY_LOCAL_MACHINE\ System\CurrentControlSet\Services необходимо поставить ссылку на кэшированный файл service_pure.exe.
- Во вкладке сервисов нужно активировать пункт Allow Service to Interact with Desktop и выбрать режим запуска “manual”.
Далее — как обычно: после перезагрузки загрузить Purify и стартовать сервис. Purify «подхватит» его и начнет тестировать так, как это было бы при работе обычного приложения.
Помимо системных сервисов Rational Purify способен работать совместно:
- с компонентами ActiveX;
- дочерними процессами;
- совместно написанными программами (Visual С++ & Visual Basic);
- COM-серверами (in-process и out-of-process);
- Internet Information Server.
Сообщения об ошибках и предупреждениях. Настройка фильтра
По своей природе Rational Purify способен выловить не только ошибки, связанные с потерей памяти, но и ряд других не менее важных ошибок. Все сообщения делятся на две категории: ошибки и предупреждения. Во время запуска этот продукт будет тщательно собирать все виды сообщений, и только настройка фильтра позволит отказаться от заведомо лишней, ненужной информации. Система фильтров Purify может настроить не только степень «придирчивости» к программе, но и количество исследуемых внешних модулей (чтобы разработчик мог концентрироваться только на собственных ошибках и не огорчался по поводу системных).
По умолчанию Purify выводит все сообщения и предупреждения, что может привести разработчика в шоковое состояние (а ведь даже в абсолютно правильной программе могут быть определенные предупреждения). Это связано со спецификой поиска неточностей, так как некоторые предупреждения могут считаться ошибкой, а могут и не считаться — все зависит от конкретного алгоритма. Вот почему Purify предлагает мощный фильтр (рис. 5).
Как видно из рисунка, предполагается ставить фильтры либо по сообщениям (вручную), либо по категориям (при этом соответствующие сообщения выбираются автоматически). Обратите внимание на список сообщений: их количество достигает 41 и растет с каждой новой версией Purify. Перед тем как перейти к рассмотрению всех сообщений, следует отметить очень важный нюанс: Purify способен работать совместно с отладчиком. В этом случае возможна двойная работа по отладке программы с установкой точек прерывания.
Попробуем рассмотреть некоторые сообщения Purify c комментариями и примерами:
- ABR: Array Bounds Read — выход за пределы массива при чтении:
#include <iostream.h> #include <windows.h> int main(int, char **) { int *ptr = new int[2]; //Определить число элементов ptr[0] = 0; ptr[1] = 1; for (int i=0; i <= 2; i++) { //ОШИБКА //ABR when i is 2 cerr << “ptr[“ << i << “] == “ << ptr[i] << ‘\n’; } delete[] ptr; return(0); }
- ABW: Array Bounds Write — выход за пределы массива при записи:
#include <iostream.h> #include <windows.h> int main(int, char **) { int *ptr = new int[2]; //Определить число элементов for (int i=0; i <= 2; i++) { //ОШИБКА ptr[i] = i; cerr << “ptr[“ << i << “] == “ << ptr[i] << ‘\n’; //ABW + ABR when i is 2 } delete[] ptr; return(0); }
- ABWL: Late Detect Array Bounds Write — сообщение указывает, что программа
записала значение перед началом или после конца распределенного блока памяти:
#include <iostream.h> #include <windows.h> int main(int, char **) { int *ptr = new int[2]; //Определить число элементов for (int i=0; i <= 2; i++) { //ОШИБКА ptr[i] = i; cerr << “ptr[“ << i << “] == “ << ptr[i] << ‘\n’; } delete[] ptr; //ABWL: ОШИБКА return(0); }
- BSR: Beyond Stack Read — сообщение указывает, что функция в программе собирается
читать вне текущего указателя вершины стека:
#include <windows.h> #include <iostream.h> #define A_LOT 256 int * setup_values(void) { int values[A_LOT]; //ОШИБКА: должен быть статичным for (int i=0; i < A_LOT; i++) { values[i] = i; } return(values); //ОШИБКА: неизвестно, что возвращать } int main(int, char **) { int *values; values = setup_values(); for (int i=0; i < A_LOT; i++) { //BSR: значения из “setup_values” //больше не находятся в стеке cerr << “element #” << i << “ is “ << values[i] << ‘\n’; } return(0); }
- BSW: Beyond Stack Write — сообщение указывает, что функция в программе собирается писать вне текущего указателя вершины стека (см. предыдущий пример).
- FFM: Freeing Freed Memory — попытка освобождения свободного блока памяти:
#include <iostream.h> #include <windows.h> int main(int, char **) { int *ptr1 = new int; // int int *ptr2 = ptr1; //ОШИБКА: должен дублировать объект, //а не копировать указатель *ptr1 = 10; *ptr2 = 20; cerr << “ptr1” << “ is “ << *ptr1 << ‘\n’; cerr << “ptr2” << “ is “ << *ptr2 << ‘\n’; delete ptr1; delete ptr2; //FFM: ОШИБКА return(0); }
- FIM: Freeing Invalid Memory — попытка освобождения некорректного блока памяти:
#include <iostream.h> int main(int, char **) { int i; delete[] &i; //FIM: не было операции new. //Освобождать нечего! return(0); }
- FMM: Freeing Mismatched Memory — сообщение указывает, что программа пробует
освобождать память с неправильным вызовом API для этого типа памяти:
#include <windows.h> int main(int, char **) { HANDLE heap1, heap2; heap1 = HeapCreate(0, 1000, 0); heap2 = HeapCreate(0, 1000, 0); int *pointer = (int *) HeapAlloc(heap1, 0, sizeof(int)); HeapFree(heap2, 0, pointer); //ОШИБКА: неправильное освобождение памяти HeapDestroy(heap1); HeapDestroy(heap2); return(0); }
- FMR: Free Memory Read — попытка чтения уже освобожденного блока памяти:
#include <iostream.h> #include <windows.h> int main(int, char **) { int *ptr = new int[2]; ptr[0] = 0; ptr[1] = 1; delete[] ptr; //ОШИБКА: (специально //сделано удаление) for (int i=0; i < 2; i++) { //FMR: ОШИБКА ДОСТУПА К ПАМЯТИ cerr << “element #” << i << “ is “ << ptr[i] << ‘\n’; } return(0); }
- FMW: Free Memory Write — попытка записи уже освобожденного блока памяти:
#include <iostream.h> #include <windows.h> int main(int, char **) { int *ptr = new int[2]; ptr[0] = 0; ptr[1] = 1; delete[] ptr; //ОШИБКА: (специально сделано удаление) for (int i=0; i < 2; i++) { ptr[i] *= i; //FMR + FMW: потому что ptr уже удален cerr << “element #” << i << “ is “ << ptr[i] << ‘\n’; //FMR } return(0); }
- HAN: Invalid Handle — операции над неправильным дескриптором:
#include <iostream.h> #include <windows.h> #include <malloc.h> int main(int, char **) { (void) LocalUnlock((HLOCAL)3);//HAN: 3 – неправильный указатель return(0); }
- HIU: Handle In Use — индикация утечки ресурсов; неправильная индикация дескриптора:
#include <iostream.h> #include <windows.h> static long get_alignment(void) { SYSTEM_INFO sys_info; GetSystemInfo(&sys_info); return(sys_info.dwAllocationGranularity); } int main(int, char **) { const long align = get_alignment(); HANDLE file_handle = CreateFile(“file.txt”, GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (file_handle == INVALID_HANDLE_VALUE) { cerr << “ОШИБКА файла\n”; return(1); } HANDLE map_handle = CreateFileMapping(file_handle, NULL, PAGE_READWRITE, 0, align, “mymap”); if (map_handle == INVALID_HANDLE_VALUE) { cerr << “Unable to create actual mapping\n”; return(1); } char *pointer = (char *) MapViewOfFile(map_handle, FILE_MAP_WRITE, 0, 0, align); if (pointer == NULL) { cerr << “Unable to map into address space\n”; return(1); } strcpy(pointer, “hello\n”); //HIU: map_handle все //еще доступный и правильный //HIU: file_handle все еще доступный //и правильный return(0); }
- ILK: COM Interface Leak — утечка COM-интерфейса:
#include <windows.h> int main(int, char **) { LPMALLOC lpIm; CoGetMalloc( MEMCTX_TASK, (LPMALLOC*)&lpIm); IMalloc_Release(lpIm); //НЕВЕРНЫЙ ЗАПРОС return(0); }
- IPR: Invalid Pointer Read — ошибка обращения к памяти, когда программа пытается
произвести чтение из недоступной области:
#include <iostream.h> #include <windows.h> int main(int, char **) { int *ptr = (int *) 0x80000000; //ОШИБКА: Указатель на зарезервированную //часть адресного пространства for (int i=0; i < 2; i++) { //IPR: Попытка обращения //к недопустимому указателю cerr << “ptr[“ << i << “] == “ << ptr[i] << ‘\n’; } return(0); }
- IPW: Invalid Pointer Write — ошибка обращения к памяти, когда программа
пытается произвести запись из недоступной области:
#include <iostream.h> #include <windows.h> int main(int, char **) { int *ptr = (int *) 0x80000000; //ОШИБКА: Указатель на зарезервированную //часть адресного пространства for (int i=0; i < 2; i++) { //IPW + IPR: Попытка обращения //к недопустимому указателю ptr[i] = i; cerr << “ptr[“ << i << “] == “ << ptr[i] << ‘\n’; } return(0); }
- MAF: Memory Allocation Failure — ошибка в запросе на распределение памяти:
#include <iostream.h> #include <windows.h> #define VERY_LARGE 3000000000 //Больше, чем можем получить int main(int, char **) { int *ptr = new int[VERY_LARGE / sizeof(int)]; //MAF: нельзя так много if (ptr == 0) { cerr << “Failed to alloc, as expected\n”; return (1); } else { cerr << “Got “ << VERY_LARGE << “ bytes @” << (unsigned long)ptr << ‘\n’; delete[] ptr; return(0); } }
- MLK: Memory Leak — утечка памяти:
#include <windows.h> #include <iostream.h> int main(int, char **) { (void) new char[1000]; (void) new char[1000]; (void) new char[1000]; (void) new char[1000]; (void) new char[1000]; //5 килобайт потерь return(0); } ИЛИ void All::OnAppAbout() { char *alex; //Указатель alex=(char *)malloc(20000); //MLK: берем, но не отдаем CAboutDlg aboutDlg; aboutDlg.DoModal(); }
- MPK: Potential Memory Leak — потенциальная утечка памяти (возникает, когда
производится операция над массивом не с нулевого элемента):
#include <iostream.h> #include <windows.h> int main(int, char **) { static char *ptr = new char[500000]; ptr += 100; //MPK: обнаружится, //как потенциально пропущенное return(0); }
- NPR: Null Pointer Read — попытка чтения с нулевого адреса:
#include <iostream.h> #include <windows.h> int main(int, char **) { int *ptr = (int *) 0x0; //ОШИБКА for (int i=0; i < 2; i++) { //NPR: ошибка доступа cerr << “ptr[“ << i << “] == “ << ptr[i] << ‘\n’; } return(0); }
- NPW: Null Pointer Write — попытка записи в нулевой адрес:
#include <iostream.h> #include <windows.h> int main(int, char **) { int *ptr = (int *) 0x0; //ОШИБКА for (int i=0; i < 2; i++) { //NPW: ошибка доступа ptr[i] = i; cerr << “ptr[“ << i << “] == “ << ptr[i] << ‘\n’; } return(0); }
- UMC: Uninitialized Memory Copy — попытка копирования непроинициализированного
блока памяти:
#include <iostream.h> #include <windows.h> #include <string.h> int main(int, char **) { char *ptr = new char[10]; char var[10]; memcpy(var, ptr, 10); //UMC предупреждение delete[] ptr; return(0); }
- UMR: Uninitialized Memory Read — попытка чтения непроинициализированного
блока памяти:
#include <iostream.h> #include <windows.h> int main(int, char **) { int *ptr = new int; cerr << “*ptr is “ << *ptr << ‘\n’; //UMR: нет значения в ptr delete[] ptr; return(0); }
В заключение перечислим основные возможности Rational Purify.
- Отслеживание ошибок доступа к памяти.
- Сбор и вывод статистики по использованию памяти.
- Использование комплексного подхода к тщательному тестированию.
- Технология OCI (Object Code Insertion), позволяющая детально отследить и выловить ошибку не только в контролируемом модуле, но и в модулях DLL сторонних разработчиков.
- Тестирование ActiveX, COM/DCOM, ODBC, DLL.
- Настраиваемый двухуровневый способ тестирования («придирчивости») приложений.
- Интеграция с Visual Studio.
- Открытый API, позволяющий разработчикам дописывать собственные модули и присоединять их.
- Совместная работа с любым отладчиком.
- Тестирование системных вызовов.
C автором этой статьи можно связаться по e-mail: novichkov@interface.ru.
КомпьютерПресс 2'2001