Ошибка при отсутствии выполняемых действий
Помните, в домино есть т.н. «пустышка» («пусто-пусто»), которая не имеет очков, но если она одна остается на руках – вот тут-то очки и появляются. Недавно одна ошибка компилятора PL/1 при трансляции большого проекта заставила меня вспомнить это правило игры. Но, обо все по порядку.
Программист пожаловался мне, что при попытке оттранслировать весь проект с ключом отладочного режима, он получил сообщение об ошибке от редактора связей! Такую ошибку еще надо постараться получить. Ведь существует четыреста сравнительно честных способов испортить адрес при выполнении программы (и получить код исключения Windows 0С0000005), но чтобы средствами языка высокого уровня получить ошибку формирования адреса в редакторе связей… Пришлось разбираться.
Выяснилось, что ошибка возникла после того, как все выполняемые действия в одном из модулей были закомментированы. Т.е. кроме описаний и заголовка процедуры в этом модуле ничего не осталось – сразу конец, та самая «пустышка». С другой стороны, ничего запрещенного в этом нет. Ведь модуль может состоять только из описаний, а процедура формально может иметь пустое множество выполняемых действий. Компилятор все равно генерирует в конце разбора процедуры неявный возврат.
Поэтому «пустая» процедура будет иметь тело из одного кода возврата (байт 0C3) и вызов такой процедуры сразу вернет управление в точку за ее вызовом, но формально никаких ошибок не будет, тем более при трансляции, что, собственно и происходило до тех пор, пока не был задан ключ компиляции, предписывающий выполнять отладочные действия, в частности организовать очередь имен последних пяти вызванных подпрограмм. Как только компилятор вставил команды записи в отладочный стек имени процедуры – произошла ошибка. Для того, чтобы объяснить, почему она произошла, придется несколько отклониться от темы.
Л.Ф. Штернберг в своей прекрасной брошюре «Ошибки программирования и приемы работы на языке ПЛ/1» анализируя недостатки языка приводит следующую аналогию: «Зерновой комбайн включает косилку, молотилку и веялку и эффективен при прямом комбайнировании. Но если его использовать сначала для косьбы в валки, а затем для обмолота, то работает треть комбайна, но горючее тратится на перемещение всего агрегата. Так и в ПЛ/1: редко когда язык используется на всю свою мощь, но «везет» на себе ЭВМ весь «агрегат» языковых средств».
При реализации «фреймворками» такая аналогия, вероятно, имеет смысл. Действительно, какая-нибудь Ява-машина должна реализовывать все возможности языка, даже, если они и не используются в конкретной программе. Но при генерации т.н. «нативного» кода это не так. В выполняемый файл можно не включать неиспользуемые возможности. В моем компиляторе это реализовано через таблицу соответствия системных вызовов (я их называю экстракодами, см. «Экстракоды при синтезе программ») и операций внутреннего представления программы. Например, в программе есть вызов встроенной функции MAX с параметрами типа float. При разборе текста программы компилятор сгенерирует операцию FMX в дереве представления программы, а эта операция в соответствии с таблицей «закажет» вызов системной подпрограммы ?FMAX. Одна операция может «заказать» вызов сразу нескольких экстракодов. Например, операция сравнения данных типа float «закажет» вызов сразу четырех экстракодов ?FC44S, ?FC44L, ?FC44R, ?FC44M, которые отличаются расположением операндов: в стеке или в переменных. Разумеется, если вызова функции MAX в программе нет – операция FMX не будет сгенерирована и в объектном модуле внешнего имени ?FMAX не появится, а, значит, в выполняемом модуле никакой системной подпрограммы не окажется.
Однако экстракоды отладочного режима не привязаны ни к каким конкретным операциям. В свое время я придумал очень остроумный (как мне казалось) выход. В отладочном режиме компилятор начинает генерировать операции «конец строки исходного текста», причем делает это только для выполняемых действий, а не для описаний. Вот к этой операции, нужной для вывода отладочной информации при выполнении программы я и «привязал» остальные системные вызовы отладки. В модуле, состоящем только из описаний, никаких концов строк не генерируется и для него режимы отладки безразличны. Никаких имен системных вызовов отладки после компиляции такого модуля не появится вообще.
Но жизнь меня перехитрила, когда пользователь создал программу-пустышку. Концов строк выполняемого кода там не оказалось. Компилятор сгенерировал вызов подпрограммы отладки, но не «заказал» его как очередное внешнее имя. Редактор связей, добравшись до этой ссылки, обнаружил, что адресация идет по «индексу внешнего имени», но никакого индекса нет и сообщил об ошибке. Язык тоже внес свою лепту в эту путаницу, разрешая не писать последний RETURN в подпрограмме (т.е. подразумевая его в конце подпрограммы по умолчанию), что и сделало возможным чистую пустышку. Если бы был написан хотя бы RETURN в конце текста, такой ошибки не возникло бы.
Кстати, сообщение от редактора связей продемонстрировало одну неприятную особенность сложных систем. Особенно ей грешат трансляторы, генерирующие не конечный код, а промежуточный, например, на ассемблере. Или активно использующие макрорасширения. Можно получить сообщение об ошибке, так сказать, не на своем уровне компетенции. Сообщение «несуществующий индекс» имело в виду ошибку в одном из способов разрешения ссылок при редактировании связей. Однако программист, конечно, ничего не знающий об этих режимах, вообразил, что у него какая-то ошибка с индексацией массивов в его программе.
Итак, все разъяснилось. В очередной раз подтвердилось, что жизнь богаче любого набора тестов. Мне просто не приходило в голову, что кто-нибудь запустит полностью пустую программу в режиме генерации отладочной информации.
Применительно к описанному случаю, конечно, и у меня были и бывают подпрограммы-пустышки без выполняемого тела, т.е. заглушки. Но они всегда были частью большого текста, где в других подпрограммах какие-то выполняемые действия все-таки были, и поэтому ошибок такого типа никогда не возникало. Да даже, если и была пустышка в отдельном файле, я никогда не транслировал ее в режиме отладки – там же отлаживать просто нечего!
Вот и возникает философский вопрос, этакий чайник Рассела для программистов. В компиляторе имеется ошибка. Но если за все время эксплуатации компилятора никто из программистов ни разу не создал условия для ее возникновения, она есть или ее нет?
Автор: Д.Ю.Караваев. 20.02.2019
Опубликовано: 2019.02.20, последняя правка: 2019.02.28 13:17
Отзывы
✅ 2019/02/20 14:09, Автор сайта #0
Вот почему этот анекдот не перестаёт быть актуальным:
«Когда программист ложится спать, он ставит рядом с собой два стакана. Один с водой — на тот случай, когда захочет пить. Второй — пустой, если пить не захочет». Добавить свой отзыв
Написать автору можно на электронную почту mail(аt)compiler.su
|