Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология Образование Политология Производство Психология Стандартизация Технологии |
Глава 10. Прочие функциональные особенности 115
Включение одного и того же файла в один модуль встречается чаще, чем вы предполагаете. Бывает, что в файл включается другой файл, в который включается третий, а в него в который включается Это не представляет проблемы, если в .h-файл входят только прототипы функций и определения ine . Считается дурного тона (и часто приводит к серь- езным ошибкам) объявлять в подключаемых файлах глобальные переменные или соз- давать в них функции. Повторных объявлений можно избежать, воспользовавшись директивой #ifdef. Она требует включения всех команд вплоть до если аргумент директивы был определен ранее действует наоборот— если аргумент не был определен).
//Проверяем, а ли MyModui Если нет, значит, файл во время //компхляпии вызывается впервые #ifndef
//Определим с тем, чтобы при следующем этого файла из той программы // файла не было
можно все, что вы разместить включаемом файле
в конце файла
Эта проверка будет проводиться во время компиляции, но не во время вы- полнения программы.
С++ Теперь вам должно быть понятно, почему во примерах, приведенных в этой книге, используются директивы include и Эти подключаемые файлы содержат объявления всех используемых в программах стандартных функций. Обратите внимание, что имена стандартных h-файлов заключены в угловые скобки, тогда как при объявлении локальных .h-файлов используются обычные ка- вычки. Единственное отличие между этими типами объявлений состоит в том, что начинает искать файлы, заключенные в обычные кавычки, в текущем каталоге (каталоге проекта), а поиск файлов, заключенных в угловые скобки, происходит в предопределенных каталогах, содержащих подключаемые файлы. С помощью на- строек проекта программист может где именно должен проводиться поиск включаемых файлов.
116 Часть II. Становимся программистами Глава Отладка программ на C++ Определение типа ошибки Использование отладочной печати Использование отладчика Первая программа BUDGET
е часто (особенно с "чайниками"), что программа идеально работает с первого раза. Крайне редко удается написать нетривиальную программу и не допустить ни одной ошибки. Чтобы избавиться от ошибок, можно пойти двумя путями. Первый — стирание программы и написание ее заново, а второй — поиск и исправление ошибки. Освое- ние первого пути я оставляю читателю, а в этой главе расскажу о том, как выследить и исправить ошибку в программе.
tfauta Можно выделить два типа ошибок: те, которые компилятор может найти, и те, ко- торые не может, Первый тип называют ошибками компиляции error). Их довольно легко найти, поскольку компилятор сам указывает место в программе, где встретилась ошибка. Правда, иногда описание ошибки бывает не совсем точным (компьютер так легко сбить с толку!), однако, зная капризы своего компилятора, трудно разобраться в его жалобах. Ошибки, которые компилятор не может найти, проявляются при запуске програм- мы и называются ошибками времени исполнения (run-time error). Их найти намного труднее, поскольку, кроме сообщения об ошибке, нет и намека на то, какая именно ошибка возникла и где (сообщения, которые генерируются при возникновении оши- бок выполнения, вполне достойны ошибочных). Для выявления "жучков" в программе обычно используется два метода. Первый добавить отладочные команды, выводящие ключевые значения в ключевых точках программы. Увидев значения переменных в месте возникновения ошибки, по- нять, что именно неправильно в данной программе. Второй метод заключается в ис- пользовании специальной программы отладчика. Отладчик позволяет отслеживать процесс выполнения программы.
использование Добавление команд вывода в ключевых точках помогает понять, что происходит в программе, и называется методом отладочной печати (иногда именуемым WRITE). Метод WRITE появился во времена, когда программы писались на языке FORTRAN, в котором вывод осуществляется с помощью команды WRITE. Глава 11. Отладка программ на C++ 117 Приведенная ниже "дефектная" программа наглядно демонстрирует применение отладочных команд. Эта программа должна считывать последовательность чисел с клавиатуры и выводить их среднее арифметическое значение. Однако она не делает поскольку содержит две ошибки, одна из которых вызывает аварийный оста- нов, а вторая приводит к неправильному результату. // — эта программа усредняла бы // ряд чисел, если бы не содержала // одну невыполнимую ошибку
argc, char* pszArgs[]) { cout << "Эта программа содержит // аккумулирует ряд чисел, пока // не введет отрицательное число, // после чего выводит
for (int = 0; ; ) i // ждет ввода следующего числа int nValue; cout << следующее cin >> nValue; // если введенное число меньше нуля... {nValue < ( // вывести среднее cout << "\пСреднее " << << "\п"; break;
// если число нуля, // сложить его с аккумулятором += nValue; } return 0; } После ввода этой программы создайте выполнимый файл (клавиша <F9>). Запустите эту программу и введите числа 1, 2 и 3, а затем -1. Вы ожидаете увидеть, что их среднее равно двум? Вместо этого очевидного результата будет выдано доволь- но непривлекательное сообщение об ошибке, показанное на рис. 11.1. Выявление "жучка" № 1 Сообщение об ошибке, приведенное на рис. выглядит весьма внушительно. На самом деле большая часть информации в этом сообщении бесполезна для нас (как и для многих других). Но во второй строке есть намек на источник ошибки: Divisio n by zero at .. . на ноль в . . .). Как можно понять, во время выполнения программы произошла попытка деления какой-то переменной на ноль (крайне информативно, не правда ли?). Определить источник ошибки не так уж
118 Часть Становимся программистами следующее Доведите следующее следующее [Введите
to signa l
ids: ies:
sel=017f sel=01bf stack: frame
stack:
Выполнение первоначальной версии программы прерывается в ре- зультате ошибки и просто. Например, процессор мог "потерять" выполняемый код и продолжить вы- полнение машинных инструкций, не относящихся к программе (такое тоже иногда случается). Процессор мог использовать инструкцию деления как повод для генера- ции сообщения об ошибке, скрыв таким образом настоящий ее источник (программы с ошибками похожи на поезд, который сошел с рельсов: программа не остановится, пока не произойдет действительно важное). О таких программах иногда говорят, что они в космос". Ну, а по- скольку к ним прикрепили отдельный ярлык, значит, они встречаются доволь- но часто. Следующим шагом будет запуск программы из среды разработчика, поскольку иногда среды разработчика, например Visual C++ или GNU C++, могут помочь в по- иске ошибки. Далее приведен пример с использованием GNU C++ (работа в Visual C++ очень похожа на описанную ниже). Откройте программу в редакторе соберите и запустите ее, нажав клавиши <Ctrl+F9>. Введите 1, 2 и 3, а затем -1, и программа снова аварийно завершится. Для устранения ошибки сначала необходимо найти, какой именно фрагмент кода ее вызывает. Зная расположение ошибки, вы сможете воспроизвести ее во время отладки, а Также будете знать, когда ошибка уже исправлена. После запуска программы и возникновения ошибки rhid e выдает окно, содержа- щее сообщение "Program exiting code ("Программа завершилась с кодом вы- хода как показано на рис. Я многого не знаю, но мне точно известно, что код нормального завершения нашей программы — 0. Невооруженным глазом видно, что этот код завершения не равен нулю, а значит, что-то пошло не так. Хотя я и не знаю, что именно означает код выхода Щелкните на кнопке и rhid e кроме окна редактирования программы откроет еще два окна. Глава11.ОтладкапрограммнаC++ 119 Search Run Options - эта усредняла // ряд чисел. ег.пи бы не // одну ttincluifc
int argc, cout « "Эта // аккумулирует // пользователь // после чего int
Program exit code: 255 1:1
F3 ZOOM F6 Next Menu Quit
Рис- Код выхода Oxff означает, что программа завершилась Вы можете не видеть трех окон одновременно, поскольку одно из них может быть скрыто другими. Чтобы переключиться в нужное окно, ис- пользуйте клавишу
В третьем окне находится сообщение об ошибке, которое было создано во время запуска программы, но мы обратимся ко второму окну, показанному на рис. 11.3.
' - Kil e Debug Options ... — // - эта программа усредняла бы // ряд чисел, если не содержала // одну невыполнимую ошибку
int argc, { cout « "Эта программа содержит // ряд чисел, пока // пользователь не введет отрицательное число, чего выводит среднее int
i n function in function
JUMP to source Next Quit Редактор способен вычислить, в какой части программы возниклаошибка Сообщение frame ("Отслеживание кадров вызовов") звучит так же, как сообщение о шпионском радиоперехвате. Система выполняет просмотр адресов функций, вызванных программой, начиная с самой первой. В данном случае
120 ЧастьII.Становимсяпрограммист ошибка находится в main {}, которая была вызвана функцией с crtl_startu p (крайне содержательно, не правда ли?). Итак, ошибка возникла в строке 28 исходного файла Это уже теплее... Обратимся к строке 28 исходного файла: << is : " // строка 2 6 << // строка 27 << "\п"; // строка 28 Думаю, никто из читателей не видит в строке 28 операции деления. Я тоже не вижу. Дело в том, что при компиляции C++ все выражения до точки с запятой в од- ну строку, т.е. строки 26, 27 и 28 являются частями одной строки. Таким образом, все команды в строках 26—28 будут рассматриваться компилятором как одна 28-я строка. Теперь вы знаете, что ошибка возникла в результате деления в строке 27. Таким образом, можно заключить, что в момент деления переменная nNums была равна ну- лю. Эта переменная должна содержать количество введенных чисел. Просмотрев программу, можно увидеть, что была инициализирована нулем, но после этого ее значение не увеличивалось. Переменная должна была увеличиваться во вре- мя выполнения оператора for, так что для правильной работы программы нужно из- менить строку, содержащую оператор for, следующим образом: =
Выявление "жучка" № 2 Теперь, когда найдена и исправлена ошибка № I, можно запустить программу, введя числа, заставившие ее в прошлый раз аварийно завершиться. На этот раз сооб- щение об ошибке не появится и программа вернет нулевой код выхода, но будет ра- ботать не так, как ожидалось. Вместо так горячо ожидаемой двойки будет выведено какое-то нелепое число. Эта содержит Введите 1 Введите 2 Введите следующее число: 3 Введите Среднее равно: 22 952 3
Как находит ошибку в исходном коде Сообщение об ошибке, полученное во время запуска программы из MS DOS или Windows, было не очень информативным (особенно по сравнению с Visual C++ или которые смогли указать, где именно возникла ошибка). Как же среды разработки находят источник ошибки? Компилятор C++ поддерживает два режима построения программ. По умолчанию C++ строит программу в так называемом отладочном режиме. При этом тор добавляет в машинный код информацию о том, к какой строке исходного кода относится та или иная машинная инструкция, и тогда при запуске программы можно узнать, что, например, строка 200 машинного кода отвечает строке 16 ис- ходной программы. При возникновении ошибки деления на ноль C++ по номеру строки машинного кода отслеживает, в каком месте исходной программы находилась ошибка. Такая отладочная информация занимает довольно много места. Поэтому при под- готовке окончательной версии программы следует указать компилятору, что гене- рация исполняемого кода должна проводиться без отладочной информации.
Глава 11. Отладка программ на C++ 121 Очевидно, какая-то из переменных — или nSum (а возможно, и обе) жит неверное значение. Для того чтобы исправить ошибку, необходимо узнать, какая именно из этих переменных содержит неверную Не помешало бы так- же знать, что содержится в nValue, поскольку она используется для под- счета суммы в Для этого воспользуемся методом отладочной печати. Чтобы узнать значения nValue. и перепишите тело цикла for так, как показано в следующем листинге: = { // ждет ввода следующего числа int nValue; cout << следующее cin >> // если введенное число меньше нуля... if (nValue < 0) { // ... тогда вывести среднее cout << " « « break;
// вывести отладочную информацию cout << "nSura = " << nSum << "\n"; cout « " << nNums << "\n"; cout << "<< nValue << "\n"; cout << "\n";
// если число больше нуля, // сложить его с аккумулятором += nValue; } Обратите внимание на то, что информация о состоянии отслеживаемых перемен- ных nValue, nSum и nNums выводится в каждом цикле. Ответ программы на ввод уже привычных 1, 2, 3 и -1 приведен ниже. При первом же проходе nSum принимает какое-то несуразное значение, хотя оно должно равнять- ся нулю (поскольку к этой переменной пока что ничего не прибавлялось). Эта программа содержит
Введите следующее = -858993460 0
Введите следующее nSum = -858993459 1 nValue= 2
Введите следующее nSum = -858993457 nNums= 2 nValue= 3
|
Последнее изменение этой страницы: 2019-04-19; Просмотров: 203; Нарушение авторского права страницы