Подтвердить что ты не робот

Зачем нужны форвардные декларации?

Возможный дубликат:
Должен ли С++ исключать файлы заголовков?

В таких языках, как С# и Java, нет необходимости объявлять (например) класс перед его использованием. Если я это правильно понимаю, это потому, что компилятор выполняет два прохода кода. Вначале он просто "собирает доступную информацию", а во втором проверяет правильность кода.

В C и С++ компилятор выполняет только один проход, поэтому в это время все должно быть доступно.

Итак, мой вопрос в основном заключается в том, почему это не делается так на C и С++. Разве это не устранило бы потребности в файлах заголовков?

4b9b3361

Ответ 1

Короткий ответ заключается в том, что вычислительная мощность и ресурсы экспоненциально возрастали между временем, установленным C, и временем, которое Java пришло через 25 лет.

Более длинный ответ...

Максимальный размер блока компиляции - блока кода, который компилятор обрабатывает в одном фрагменте, будет ограничен объемом памяти, который имеет компьютер компиляции. Чтобы обрабатывать символы, которые вы вводите в машинный код, компилятор должен хранить все символы в таблице поиска и ссылаться на них, поскольку они попадают в них в ваш код.

Когда C был создан в 1972 году, вычислительные ресурсы были намного более скудными и с высокой премией - память, необходимая для хранения сложной программной цельной символической таблицы сразу, просто не была доступна в большинстве систем. Исправленное хранилище также было дорогостоящим и чрезвычайно медленным, поэтому идеи, подобные виртуальной памяти или хранящие части символической таблицы на диске, просто не позволили бы компиляции в разумные сроки.

Лучшее решение проблемы заключалось в том, чтобы разбить код на более мелкие части, получив человеческий вид, какие части таблицы символов нужны, в каких компиляционных единицах раньше времени. Наложение довольно небольшой задачи на программиста, объявляющего, что он будет использовать, спасло огромные усилия, связанные с тем, чтобы компьютер искал всю программу для чего-либо, что мог использовать программист.

Он также сохранил компилятор от необходимости делать два прохода в каждом исходном файле: первый для индексации всех символов внутри, а второй - для анализа ссылок и поиска их. Когда вы имеете дело с магнитной лентой, где время поиска измеряется в секундах, а пропускная способность чтения измеряется в байтах в секунду (не килобайтах или мегабайтах), это было довольно значимым.

С++, созданный почти 17 лет спустя, был определен как надмножество C и поэтому должен был использовать тот же механизм.

К тому времени, когда Java развернулась в 1995 году, средним компьютерам хватило памяти, что наличие символической таблицы даже для сложного проекта больше не было существенным бременем. И Java не был предназначен для обратной совместимости с C, поэтому ему не нужно было использовать устаревший механизм. С# также не был ограничен.

В результате их дизайнеры решили переключить нагрузку на разделение символической декларации с программиста и снова положить ее на компьютер, поскольку ее стоимость пропорциональна суммарному усилию компиляции была минимальной.

Ответ 2

Итог: в технологии компилятора были успехи, которые делают излишние объявления ненужными. Плюс компьютеры в тысячи раз быстрее, и поэтому могут сделать дополнительные вычисления необходимыми для устранения недостатков форвардных объявлений.

C и С++ старше и стандартизованы в то время, когда необходимо было сохранить каждый цикл ЦП.

Ответ 3

Нет, он не будет обрезать файлы заголовков. Это исключило бы требование использования заголовка для объявления классов/функций в одном файле. Основная причина заголовков - не объявлять вещи в одном файле. Основной причиной заголовков является объявление объектов, которые определены в других файлах.

К лучшему или худшему, правила семантики C (и С++) задают поведение стиля "один проход". Например, рассмотрим такой код:

int i;

int f() { 
     i = 1;
     int i = 2;
}

i=1 присваивает глобальный, а не тот, который определен внутри f(). Это связано с тем, что в точке назначения локальное определение i еще не было замечено, поэтому оно не учитывается. Вы все равно можете следовать этим правилам с помощью двухпроходного компилятора, но сделать это может быть нетривиальным. Я не проверял их спецификации, чтобы знать с уверенностью, но я предполагаю, что Java и С# отличаются от C и С++ в этом отношении.

Изменить: поскольку комментарий сказал, что моя догадка неверна, я немного проверил. Согласно Справочнику по языку Java, §14.4.2, Java, похоже, довольно близка к тем же правилам, что и С++ (немного другое, но не много.

По крайней мере, когда я прочитал спецификацию языка С#, (предупреждение: файл Word), однако это другое. Он (§3.7.1) говорит: "Область локальной переменной, объявленной в объявлении локальной переменной (§8.5.1), является блоком, в котором происходит объявление."

Это говорит о том, что в С# локальная переменная должна быть видимой во всем блоке, в котором она объявлена, поэтому с кодом, подобным приведенному в примере, присваиванием будет локальная переменная, а не глобальная.

Итак, моя догадка была наполовину правильной: Java следует (довольно много) то же самое правило, что и С++ в этом отношении, но С# не делает.

Ответ 4

Это связано с меньшими модулями компиляции в C/С++. В C/С++ каждый файл .c/.cpp скомпилирован отдельно, создавая модуль .obj. Таким образом, компилятору нужна информация о типах и переменных, объявленных в других модулях компиляции. Эта информация предоставляется в виде форвардных объявлений, обычно в файлах заголовков.

С#, с другой стороны, компилирует несколько файлов .cs в один большой модуль компиляции сразу.

Фактически, при ссылке на разные скомпилированные модули из программы на С# компилятор должен знать объявления (имена типов и т.д.) так же, как это делает компилятор С++. Эта информация получается непосредственно из скомпилированного модуля. В С++ одна и та же информация явно разделена (почему вы не можете найти имена переменных из С++ - скомпилированной библиотеки DLL, но можете определить ее из сборки .NET).

Ответ 5

Передовые декларации на С++ - это способ предоставить метаданные о других фрагментах кода, которые могут быть использованы скомпилированным исходным кодом для компилятора, поэтому он может генерировать правильный код.

Эти метаданные могут исходить от автора связанной библиотеки/компонента. Однако он также может быть сгенерирован автоматически (например, есть инструменты, которые генерируют заголовочные файлы С++ для COM-объектов). В любом случае, С++ способ выразить эти метаданные через файлы заголовков, которые необходимо включить в исходный код.

С#/.NET также использует похожие метаданные во время компиляции. Однако эти метаданные автоматически генерируются, когда сборка, к которой она применяется, построена и обычно внедряется в нее. Таким образом, когда вы ссылаетесь на свой проект С# на сборку, вы, по сути, говорите компилятору "ищите метаданные, которые вам нужны в этой сборке, пожалуйста".

Другими словами, генерация и потребление метаданных на С# более прозрачна для разработчиков, что позволяет им сосредоточиться на том, что действительно важно - написание собственного кода.

Существуют и другие преимущества, связанные с наличием метаданных о коде, поставляемом вместе с сборкой. Отражение, испускание кода, сериализация "на лету" - все они зависят от метаданных, чтобы иметь возможность генерировать правильный код во время выполнения.

Аналогом С++ для этого будет RTTI, хотя он не получил широкого распространения из-за несовместимых реализаций.

Ответ 6

От Эрика Липперта, блоггера всех вещей, внутренних для С#: http://blogs.msdn.com/ericlippert/archive/2010/02/04/how-many-passes.aspx:

Язык С# не требует, чтобы декларации происходят до использования, который имеет два воздействия, опять же, на пользователя и писателя компилятора. [...]

Воздействие на автора компилятора что мы должны иметь "два прохода", компилятор. На первом проходе мы смотрим для деклараций и игнорирования органов. Как только мы собрали все информацию из заявлений, которые мы получили бы от заголовков в С++, мы делаем второй проход по кода и генерировать IL для тел.

Подводя итог, использование чего-то не требует объявления его в С#, тогда как в С++. Это означает, что в С++ вам нужно явно объявлять вещи, и более удобно и безопасно делать это с файлами заголовков, чтобы вы не нарушали One Definition Правило.