504 Часть III: Управление проектами и группами
программа разрушается. Но даже если язык программирования позволяет
процедуре обслуживать несколько процессов, эта возможность сопряжена
с целым рядом проблем. В частности, как раздельно хранить данные, пред-
назначенные для различных процессов, чтобы один процесс не смог раз-
рушить данные другого?
Имена переменных, содержащие имена
команд
Некоторые языки программирования или их диалекты игнорируют
пробелы. В таком языке строка PRINTMYNAME может быть интерпрети-
рована как PRINT MYNAME. Выполняя эту команду, программа попыта-
ется распечатать содержимое переменной с именем MYNAME. Если
программист на самом деле хотел определить переменную с именем
PRINTMYNAME, эта попытка приведет к ошибке. Как правило, подобные
ошибки выявляют сами программисты, но некоторые из них все же сохра-
няются в программах.
Неверное предположение о состоянии
программы или данных после вызова
Предположим, что в программе имеется процедура, предназначенная
для установки определенного параметра внешнего устройства, например,
его скорости передачи информации. Программа вызывает эту процедуру и
полагает, что она успешно выполнила свою работу. Она немедленно начи-
нает передачу данных. Однако на этот раз установка скорости не была
выполнена. В результате не удается и передача данных, а программа "за-
висает" в ожидании ответа устройства.
Другим примером может быть выполнение подпрограммы, масштабиру-
ющей данные и возвращающей число между 1 и 10. При определенных
обстоятельствах эта подпрограмма ведет себя нестандартно, возвращая
масштаб в диапазоне от 0 до 10. Поскольку вызывающая подпрограмма не
ожидает получить 0, она аварийно завершается при попытке деления на 0.
Обработка ошибок выполнения процедур
Предположим, что процедура вычисляет квадратный корень из передан-
ного ей числа. Если ей передано отрицательное число, она ничего не вы-
числяет и перед завершением устанавливает флаг ошибки. В данном случае
флаг ошибки используется для того, чтобы вызывающая процедура сама
могла решить, что ей делать с проблемой. В одном случае может быть
выведено сообщение об ошибке, в другом отображена подсказка, а в тре-
тьем число может быть передано менее скоростной подпрограмме, вычис-
ляющей квадратные корни из комплексных чисел. Процедуры,
Приложение: Распространенные программные ошибки 505
возвращающие информацию об ошибках в виде флагов, могут вызываться
в самых разных ситуациях. В любом случае вызывающая подпрограмма
обязательно должна проверить, выполнила ли процедура то, что предпола-
галось программистом. Однако, если ошибка очень маловероятна, програм-
мист может забыть о проверке результата. При тестировании, если ошибка
все же произойдет, она может показаться невоспроизводимой.
Возврат не в ту точку кода
Главным отличием между вызовом подпрограммы и переходом по ко-
манде GOTO является то, что из подпрограммы управление всегда возвра-
щается в точку вызова, в то время как после перехода по GOTO возврат
вообще не выполняется. Однако иногда управление после вызова подпрог-
раммы может быть возвращено не в то место программного кода.
Испорченный стек
После завершения работы подпрограммы управление передается коман-
де, непосредственно следующей за командой вызова. Для хранения адре-
са этой команды используется структура данных, называемая стеком (stack).
В верхней ячейке стека хранится адрес, помещенный туда последним.
Именно по нему и возвращается управление после завершения подпрограм-
мы. Как правило, стек используется не только для хранения адресов воз-
врата, но и для временного хранения некоторых данных.
Если подпрограмма помещает в стек некоторые данные и не удаляет их
перед своим завершением, в верхней ячейке стека будет вовсе не адрес
возврата. Однако компьютер этого не узнает и выполнит передачу управ-
ления, интерпретировав то, что он найдет в стеке, как адрес. В результате
управление будет передано совершенно другому участку памяти. Если там
окажутся данные, сбой произойдет немедленно, а если какой-то код, про-
грамма начнет делать что-то не то и, скорее всего, тоже вскоре аварийно
завершит работу.
Переполнение и выход за нижнюю границу стека
Стек обычно предназначен для хранения фиксированного количества
адресов — например, не более 16, 32 или 128. Предположим, что стек
вмещает не более двух адресов. Программа вызывает процедуру 1 и сохра-
няет в стеке адрес возврата. Затем процедура 1 вызывает процедуру 2 и
тоже сохраняет адрес возврата. Когда процедура 2 завершает свою работу,
управление возвращается процедуре 1, а по ее завершении — главной про-
грамме.
Но что будет, если процедура 2 вызовет процедуру 3? В стеке уже хра-
нятся два адреса, и адрес возврата из процедуры 3 в него не поместится.
Такая ситуация называется переполнением стека (stack overflow). Обычно