Искусство программирования на Си (984073), страница 22
Текст из файла (страница 22)
Но я действительно сторонник вообще программ Ипй и настоятельно рекомендую выбрать какую-нибуд( из них, подходящую для вашей конкретной платфорь(ы, и использовать ее для всех новых исходных кодов (и~для старых тоже, если у вас есть достаточно врсменг(, Ьнергии и энтузиазма, чтобы вернуться назад и испРавить их). Поиск и исправление распространенных ошибок Давайте четко разграничим тестирование и отладку.
Обе процедуры сложны и требуют мастерства, обе необходимы и при этом очень сильно отличаются друг от друга. Рассмотрим пару рабочих определений. Тестирование — это искусство вывести программу из строя, когда вы думаете, что она работает. Отладка — это искусство исправить программу, о тирование, тем более длительным будет использование программы. Не слишком приятно услышать от тестирующего о неадекватных действиях программы, поэтому имеет смысл потратить время и самому убедиться, что все в порядке. Конечно, если программа только что написана, в ней обязательно есть ошибки (если только это нс действительно тривиальная программа, наличие ошибок в которой очень маловероятно).
Поэтому стоит повторить выполнение программы несколько раз и удостовериться, что она в полной мере отвечает своему назначению. Это кажется очевгшным, однако далеко не все программисты так поступают. Некоторые отправляют программы в систему управления версиями, даже не компилируя их. Они сами напрашиваются на неприятности, и неприятности не заставят себя ждать.
(Я это точно знаю, поскольку (стыд мне и позор!) и сам так делал.) Когда вы впервые запустите свою программу, то, работы программы, намного легче локализовать проблему, чем если ответственность за один и тот же процесс разделена между несколькими функциями или даже исходными файлами. Модульное построение программы имеет и другие преимушества.
Например, если вы изолируете непереносимую часть программы в отдельные модули и обеспечите связь с ними только через хорошо определенные интерфейсы, то перенести программу на другой компьютер будет сравнительно просто. После того как вы определили, в каком модуле, скорее всего, заключается проблема, следует обратиться к исходному коду. Вероятно, таким образом определяется местоположение 90% всех ошибок. Но не слишком задерживайтесь на этом. Если вы не можете найти ошибку в исходном кодс за пару минут, значит, вы действительно не знаете, где она.
Следите за временем. Если вы не можете найти ошибку, выполняя прогонку в течение двух или, может быть, трех минут и используя сгюбше- Когда вы установили набор вводимых данных, вызывающий ошибку, а также немного отличающийся набор данных, который се не вызывает, можете строить гипотезы. Не старайтесь ограничиться только одной гипотезой. Наступает очередь бумаги и ручки (или.текстового редактора и ваших пальцев). Поныл айтесь придумать 5-)0 возможных причин ошибки, нс пытаясь оценить их при этом.
Когда список гипотез составлен, вы можете оцределить условия однозначной проверки каждой гипотезы. Это может включать только проверку вашего исходного кода, или потребуется создать новый тестовый вариант. - Можно ограничить рабочий объем, особенно при тестировании больших систем с большим количеством вводных данных, вьшелив сомнительный модуль и поместив его в специально написанную тестовую программу.
Такая программа обычно состоит из функции ша!пп, подозрительной подпрограммы, все остальное должно быть сокращено до минимума. Таким образом вен Иоеныеенне кадо нраерамм Переематреннан язан С Часть 1 Глава 7 геевгв яшвз ) бочин день, твк и не найдя ошибки, в решение проблемы пришЛо ввм В голову в машине по дороге домой, то вы знаете цену отстранения от проблемы. (Этот метод применим не только к отладке программ. Это хорошо известная техника решения кроссвордов, в также большинства других проблем.) Консультация ваших коллег также может быть полезна при выяснении будущего вашей программы.
Свм процесс объяснения проблемы другому человеку в детвпя», доствточньж дпя того, чтобы он мог помочь ввм, часто может быть хорошим толчком и дпя вашего умв. Если рядом нет подходящего человеческого существа, испопьзуите любимого плюшевого медвежонка. И зто помогает( Достаточно даже, если вы убедите себя хотя бы нв время, что ваш собеседник спущает и понимает ввс. Его роль чисто символическая. Он не должен ничего говорить. Моего игрушечного медвежонка зовут Клинт. Если вы чувствуете себя глупо, разговаривая с бессловесным плюшевым медвежонком, найдите запасные очки (подойдут солнцезащитные) и наденьте их нв медвЕжонка.
ТЕ- перь вы разговариваете с (явно) умным плюшевым мед- ВвжйиКОМЕ И,атае СДШЕдтДВЕННО,МЕЛЯЕт,ДЕДОМ„,Я,НЕ ЗНЕЮ Ошибки завышения (или занижения) значения на единицу сьаг *всгдвр(сваг 'в) ( ге1вгв всгсру(ва11ос(вег1ев(в)), в)з ) Этот кол демонстрирует лтножество проблем.
Вопсрвых, он вторгается в пространство имен реализации. Кроме того, он использует возвращаемое функцией гяа!)ос значение без предварительной проверки того, что запрос на размещение был удовлетворен, и беззаботно предполагает, что в не равно Х(/).).. В 97 случаях из 100 (да, я слезал такой подсчет) ни одна из этих проблем не привелет к сбою программы. Но здесь есть и другая проблема.
Для з выделено недостаточно места. Это происходит из-за того, что функция в(г)еп возвращает число символов в строке, не учитывая символ нуль в ес конце. Поэтому з(геру изменяет область памяти, нс принадлежа)трбю программе. Это классическая ошибка за- Проблема заключается в операторе условия цикла. Он выглядигн нормально, не так лиу Но, конечно, это нс так.
Должно быть написано СОГ (Х = О; 1 < Пешмз.ввя — 1; Х++) Чтобы в максимальной степени предотвратить ошибки, следовало бы проверить значение ввпм(ешз еше до цикла и убедиться, что оно не равно 0 или !. Это один из вариантов ошибки нарушения границы. Все ошибки нарушения границ являются ошибками завышения или занижения значения на единицу, обратное неверно. К сожалению, эти конкретные ошибки почти неизменно приволят к использованию не принадлежащей программе памяти, поэтому оии часто являются причиной кажущегося случайным поведения программы.
., бюешсчкьомнеючпнмынее> цшиыды выполняется операция ей++, то сй получает значение 256, которое приводится к диапазону конкретного типа данных (это не случай переполнения, вызванного неопределенным поведением; подобное приведение определено стандартом для типов без знака). Таким образом, значение сй изменятся с 256 на 0 (поскольку 256%256 равно 0), и выполнение цикла продолжается. Реально выполнение программы может прекратиться, когда писк будет полностью заполнен. Если, конечно, вам повезет. Присваивание вместо сравнения Это противная ошибка.
В языке С каждое выра:кение, определенное иначе, чем уо(д, имеет свое значение, которве может быть использовано в других выражени- ях. Это может привести к следующей проблеме: воок *выводе(воок *ноас, сваг *авва) ЙШ = вегсвр(дата, гоое->двеа)З 1Е(д(11 > 0) Раа1[е]' Непроюепиекодо проераин Глава 7 Ш)ЯЙ Тс, кто использует компиляторы, прсдостсрсгаюшис Листинг 7.2. Переполнение буфера. Листинг 7.3.
Нарушение границ массива фиксированной длины. против использования операторов присваивания внут- [ „ < )1пс1пде <вса1о.Ь> ри операторов [[[) и иЬ!]еп, часто говорят, что лучше Ф1пс1пде <пешая.Ь> писать ясный код и верят, что компилятор прсдупрс- зпс па1п(но10) дит об опсчаткс, а также утверждают, что [гоо[->)ей == зпг паза(но1о) ( [п][ЛХ) легче читать, чем (]кЛ/).[. == гоо(->!ей).Тс жс, сЬаг СЬагасгегвес(128) = (О); /' вввцваппзпруем все епемеегм массвва в 0 ° / сЬаг авве[12]; гпс сЬ; ' О РЕГУЛЯРНО ИСПОЛЬЗУ ' бОЛЕЕ ОДНОГО КОМ ЯТОРа, сьаг Рг ее[12]. 1пг меньше в этом уверены. Некоторые компиляторы про- сьаг засола[12]; сто нс выполняют проверку такого рода.
мЫ1е((сЬ Оегсьаг()) != ВОР) рг1псу( Рзгвс павет[п ); ( Переполнение буфера сяесв(Р1гас. а1гео( Рггвг, вы1п); +есьагасгегяес(сЬ); ргрпсг( Ьпвс павет[п"); ) Начинаюшис С-программисты быстро понимают, что свеса(яесопд, в1пеоу Оесопд, в101п); так делать нельзя: ргрпсу([ОЫ]СЬаг Ргеясепсу[п(ОЫ]); вггсас(вале, Р1гвс)> ргрпсс ( [6Ь1) — — — 1п [ ДЫ ] ]; 01пс1пое <псаьо. ь> вггсас(вале, ); [ог(1 = 0; з < а[гесс сьагасгегвезм 1еп] )1п<1пе)е <вгг1пО.Ь> аггсас(ваве, Яесопд); ( 1пг ва1п(но10) рггпсу([ОЫ]ВЗО Вд[п[ОЫ), г, СЬагассегяес[з]); рг1пеу('Рп11 папе: Вв1п', Иаве); сьаг *в; геспгп 0; птгсру(в, *Ие11о вог161")З геспгп 0; ) опфгт: Перегм отрена ой язык С ттепроазеное кода программ Часть! Глава 7 ни старался.