Леша на полке клопа нашел
Sep. 2nd, 2024 12:35 am![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Короче, был такой программист по имени Эдсгер Дейкстра, который вместе с коллегой реально написал за первую половину 1960 года первый в мире компилятор с Алгола-60, по слухам, не имевший ошибок. Но не на таких напал, как мы с
vak.
В Алголе-60 были встроенные в язык элементарные функции, типа abs, sin, cos, exp, ln и пр., которые компилировались особым образом - грубо говоря, примерно как операция изменения знака числа, которая одно число на входе имеет, и одно число на выходе выдаёт, разве что машинных команд для вычисления результата нужно больше.
Также в Алголе-60 была возможность передавать имена процедур и функций в качестве параметров других процедур. Это удобно, например, для определения алгоритмов, наподобие ВычислиИнтеграл(функция, начало, конец, шаг).
Формально в качестве функции, передаваемой в качестве аргумента, должна быть допустима любая - как определенная программистом в программе, так и элементарная, встроенная в язык. Но из-за того, что способ вызова элементарных функций и функций, определенных в программе, был принципиально разным, большинство авторов компиляторов этим делом не заморачивались, и попросту не позволяли передавать элементарные функции как параметры, и если написано, например, слово sin, то после него обязательно должна быть открывающая скобка, иначе ошибка. А если надо вычислить интеграл от синуса, то изволь писать не
ВычислиИнтеграл(sin, начало, конец, шаг);
а сначала что-то типа
_real _function SIN(x); _real x; SIN := sin(x);
и только потом
ВычислиИнтеграл(SIN, начало, конец, шаг);
Но Дейкстра был перфекционист, и хотел сделать всё честно, чтобы можно было передавать встроенные функции в процедуры-алгоритмы. И сделал: если при вызове такой процедуры очередной параметр состоял исключительно из идентификатора встроенной функции, то тривиальная обёртка наподобие вышеприведенной генерировалась автоматически.
Всё бы хорошо, но тут и таилась ошибка: переменная, в которой хранился последний прочитанный идентификатор (точнее, его номер в таблице имён), после этого принудительно не сбрасывалась, отчего, если следующий параметр не был выражением и не содержал никаких идентификаторов, т. е. был просто числом, то проверка и следующего параметра, не является ли он идентификатором встроенной функции, тоже была успешной, и генерировалась обёртка, в которой в процессе выполнения программы пытались, грубо говоря, выяснить, с каким параметром нужно вызвать число, что приводило к краху.
Возможно, факт наличия этой ошибки был известен, но это вряд ли, потому что будучи замечена, исправляется она элементарно: достаточно сразу после генерации кода обёртки устанавливать в переменную какое-нибудь значение, чтобы проверка упомянутой переменной на соответствие встроенной функции больше не возвращала успех.
![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
В Алголе-60 были встроенные в язык элементарные функции, типа abs, sin, cos, exp, ln и пр., которые компилировались особым образом - грубо говоря, примерно как операция изменения знака числа, которая одно число на входе имеет, и одно число на выходе выдаёт, разве что машинных команд для вычисления результата нужно больше.
Также в Алголе-60 была возможность передавать имена процедур и функций в качестве параметров других процедур. Это удобно, например, для определения алгоритмов, наподобие ВычислиИнтеграл(функция, начало, конец, шаг).
Формально в качестве функции, передаваемой в качестве аргумента, должна быть допустима любая - как определенная программистом в программе, так и элементарная, встроенная в язык. Но из-за того, что способ вызова элементарных функций и функций, определенных в программе, был принципиально разным, большинство авторов компиляторов этим делом не заморачивались, и попросту не позволяли передавать элементарные функции как параметры, и если написано, например, слово sin, то после него обязательно должна быть открывающая скобка, иначе ошибка. А если надо вычислить интеграл от синуса, то изволь писать не
ВычислиИнтеграл(sin, начало, конец, шаг);
а сначала что-то типа
_real _function SIN(x); _real x; SIN := sin(x);
и только потом
ВычислиИнтеграл(SIN, начало, конец, шаг);
Но Дейкстра был перфекционист, и хотел сделать всё честно, чтобы можно было передавать встроенные функции в процедуры-алгоритмы. И сделал: если при вызове такой процедуры очередной параметр состоял исключительно из идентификатора встроенной функции, то тривиальная обёртка наподобие вышеприведенной генерировалась автоматически.
Всё бы хорошо, но тут и таилась ошибка: переменная, в которой хранился последний прочитанный идентификатор (точнее, его номер в таблице имён), после этого принудительно не сбрасывалась, отчего, если следующий параметр не был выражением и не содержал никаких идентификаторов, т. е. был просто числом, то проверка и следующего параметра, не является ли он идентификатором встроенной функции, тоже была успешной, и генерировалась обёртка, в которой в процессе выполнения программы пытались, грубо говоря, выяснить, с каким параметром нужно вызвать число, что приводило к краху.
Возможно, факт наличия этой ошибки был известен, но это вряд ли, потому что будучи замечена, исправляется она элементарно: достаточно сразу после генерации кода обёртки устанавливать в переменную какое-нибудь значение, чтобы проверка упомянутой переменной на соответствие встроенной функции больше не возвращала успех.