Freeman писал(а):
В языках типа C++ или Кантора необходимость нескольких проходов будет определяться предварительным объявлением процедур/функций/методов, а не переменных. В C++ это решено заголовочными файлами, в Паскале -- интерфейсными секциями и ключевым словом forward, а в Канторе еще не решил как. Смотрю на Java, в которой ни заголовочных файлов, ни предварительных объявлений нет. Кто может сказать, сколько проходов делает компилятор Java?
Добавлю ещё от себя. В Аде почти каждый модуль делится физически на два файла: один содержит спецификацию модуля (объявления публичных типов, переменных, процедур и т.п.), второй -- тело этого модуля. В общем и целом это ближе всего к Турбо Паскалю/Дельфям с той разницей, что части интерфейса и реализации физически разнесены в отдельные файлы. Вроде бы теоретически в определённых ситуациях такое разделение является полезным и даёт несколько б
ольшие возможности, чем разделение одного файла на части интерфейса и реализации, но я с таким не сталкивался.
В плане синтаксиса самих объявлений мне Ада в целом больше нравится, чем Паскаль, так как в ней объявление типа записи (структуры) в общем случае делится на две части -- публичную и приватную. Физически обе размещаются в спецификации модуля (ведь, не имея полного описания, компилятор не может определить размер типа, а значит, не может выделять память под соответствующие переменные, даже если собственно работа с ними будет идти исключительно через подпрограммы модуля, где определён тип), однако находятся в разных его частях (публичной и приватной). Благодаря этому описание публичной части получается коротким, содержащим лишь объявления собственно публичной части, а все "глубоко интимные" моменты типа описаны отдельно. В Паскале, как и в Це++, публичная и приватная части объявляются вместе, просто разделяются ключевыми словами public и private, из-за чего объявление типа может быть очень громоздким, а сие затрудняет его "внешнее" использование (когда приватная часть объявления не нужна и лишь мешает программисту, отвлекая на себя внимание). Естественно, возможность внесения исполнимого кода (тел функций) в объявление типа, что допускает Це++, ещё более ухудшает и без того невысокую читаемость его текстов. Так что здесь я безусловный сторонник адского подхода -- наиболее полного отделения "мух от котлет".
Что же до числа проходов, то при наличии предварительных объявлений (что является обязательным требованием в Паскале и Аде, да вроде и в Це++; в просто Це функции могут не объявляться заранее, но тогда типы их параметров компилятор определяет сам, глядя на вызов функции), то компиляция гарантированно может быть выполнена за один проход. Правда, это исключает сколько-нибудь глубокую оптимизацию (максимум, что может сделать однопроходный компилятор, -- это выполнить локальную оптимизацию выражений вроде свёртки констант или выноса общих подвыражений), но зато позволяет очень быстро получить готовый к выполнению код. Но здесь тоже может быть сложность, связанная с архитектурными особенностями той или иной машины. В частности, далеко не все архитектуры позволяют прямо адресовать далеко расположенные объекты, и тогда быстро сгенерированный код может оказаться неработоспособным из-за того, что итоговые адреса лежат слишком далеко друг от друга. Но это можно обойти: поскольку эффективность кода всё равно очень низка, можно генерировать более длинные последовательности команд, гарантирующие досягаемость любой нужной точки (путём, например, неограниченной косвенной адресации вместо ограниченной прямой).