2010-04-13 4 views
15

Можно создать дубликат:
Should C++ eliminate header files?Зачем нужны передовые декларации?

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

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

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

+0

C++ компилятор будет читать последовательно [то есть, он будет читать сверху вниз. ...] Вот как работает язык. Тогда ваше предложение «пройти» через два и посмотреть на прототипы функций будет работать, но, к сожалению, это не так, как работает язык. – Warty

+2

Дубликат: http://stackoverflow.com/questions/752793 –

+4

C как язык был стандартизирован более 30 лет назад, когда базовая технология была и гораздо менее способна и намного дороже, чем сегодня. Нежное предложение: попытайтесь немного узнать об истории вещей и поймите что мир сильно изменился с тех пор е решения были приняты и будут продолжать меняться после того, как вы сделаете выбор в своих усилиях в области развития. Кто-то, когда-нибудь, * заинтересует «WTF ?!» о ваших решениях .... ;-) – DaveE

ответ

39

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

Чем дольше ответ ...

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

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

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

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

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

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

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

+7

Отличное резюме. Напомнил мне о «добрых старых днях», составляющих программу на двух флоппи-дисководах 640 тыс. ПК. Принял около 10 минут с полдюжиной или более гибкими изменениями. Все это для программы, содержащей не более пары сотен заявлений! И думал, что я на небесах со всей этой силой. – NealB

+4

Отличный ответ, всегда приятно получить какую-то историческую перспективу для нас, молодых людей! –

5

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

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

+2

:-) Другими словами - C# лучше, чем C++. –

+8

Вам не хватает ключевых слов здесь: обратная совместимость. Ваша последняя строка заставляет это звучать так, как C и C++ имеют только одну версию стандарта от каменных век. Он должен читать «и был * первым * стандартизован ... и поддерживать обратную совместимость, метод остается тем же». @Franci: Когда вы закончите писать ОС на C#, приходите за мной. – GManNickG

+3

@Franci: Нет ... другими словами, современные компиляторы языка сделали декларации устаревшими, потому что им не нужно беспокоиться о обратной совместимости. Это можно сделать в C++. Получайте удовольствие от написания аппаратных драйверов в буфере C#. –

1

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

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

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

+0

К сожалению, это не объясняет, как C# управляет нашим 100-сборочным решением, содержащим тысячи исходных файлов и миллионы строк кода, лучше, чем C++ управляет одним.h (что может все еще нуждаться в прямом объявлении, хотя вся необходимая информация находится в одном файле). –

+0

@ Джейсон: преимущества отдельной компиляции разные: когда вы меняете только реализацию, ваша перекомпиляция будет почти мгновенной. (Конечно, это делает самую первую компиляцию медленнее.) Я не знаю, почему ваш C++ не может управлять одним заголовочным файлом, я никогда не сталкивался с какими-либо проблемами с моими. – Vlad

+0

@ Vlad: Вы сказали: «... компилятору нужна информация о типах ... объявлена ​​в других модулях компиляции», но на самом деле даже один класс C++ в одном заголовке * может * требовать предварительной декларации - т.е. даже в пределах одного модуля компиляции , То есть C++ анализирует линейным способом через код (таким образом, требуя предварительной формулировки будущих типов, когда они ссылаются), в то время как C# эффективно создает базу данных для базы кода, которая позволяет ему иметь произвольный доступ ко всем типам. –

0

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

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

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

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

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

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

0

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

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

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

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

3

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

К сожалению, правила семантики C (и C++) задают поведение стиля «один проход». Просто для примера, рассмотрим такой код:

int i; 

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

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

Редактировать: Поскольку комментарий сказал, что моя догадка неверна, я немного проверил. В соответствии с Java Language Reference, §14.4.2, Java, кажется, следует довольно близко к тем же правилам, что и C++ (немного по-другому, но не много.

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

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

Итак, мое предположение было наполовину прав: Java следующим образом (довольно much0 такое же правило, как C++ в этом отношении, но C# не

+0

Ваша догадка неверна. –