Я думал о том, почему существуют (во всех изученных мною языках программирования, таких как C ++, Java, Python) стандартные библиотеки, такие как stdlib, вместо того, чтобы иметь подобные «функции», являющиеся примитивом самого языка.
Я думал о том, почему существуют (во всех изученных мною языках программирования, таких как C ++, Java, Python) стандартные библиотеки, такие как stdlib, вместо того, чтобы иметь подобные «функции», являющиеся примитивом самого языка.
Ответы:
Позвольте мне немного подробнее рассказать о хорошем ответе @ Vincent (+1) :
Почему компилятор не может просто перевести вызов функции в набор инструкций?
Это можно сделать через два механизма:
встраивание вызова функции - во время перевода компилятор может заменить вызов исходного кода своей реализацией непосредственно в строке вместо фактического вызова функции. Тем не менее, функция должна иметь определенную реализацию, и это может быть в стандартной библиотеке.
встроенная функция - встроенные функции - это функции, о которых сообщил компилятор, не обязательно находя функцию в библиотеке. Они обычно зарезервированы для аппаратных функций, которые практически недоступны каким-либо другим способом, поскольку они настолько просты, что даже накладные расходы на вызов функции библиотеки на ассемблере считаются высокими. (Компилятор, как правило, может только автоматически встроить исходный код на своем языке, но не в ассемблерные функции, в которые вступает внутренний механизм.)
Тем не менее, несмотря на это, иногда лучшим вариантом для компилятора является перевод вызова функции на исходном языке в вызов функции в машинном коде. Рекурсия, виртуальные методы и размер - некоторые причины, по которым встраивание не всегда возможно / практично. (Другая причина - намерение сборки, например, отдельная компиляция (объектные модули), отдельные модули загрузки (например, DLL)).
Нет никакого реального преимущества в том, чтобы сделать большинство стандартных библиотечных функций интризами (которые бы жестко кодировали гораздо больше знаний в компилятор без реального преимущества), поэтому повторный вызов машинного кода часто является наиболее подходящим.
C является известным языком, который, возможно, пропустил другие явные языковые утверждения в пользу стандартных библиотечных функций. Хотя библиотеки уже существовали, этот язык перешел к выполнению большего объема работы со стандартными библиотечными функциями, а не в качестве явных утверждений в грамматике языка. Например, IO в других языках часто получает собственный синтаксис в форме различных операторов, в то время как грамматика C не определяет какие-либо операторы IO, просто вместо этого откладывается до своей стандартной библиотеки, чтобы обеспечить ее доступность через вызовы функций, которые компилятор уже знает, как это сделать.
Это просто для того, чтобы язык был максимально простым. Необходимо различать функцию языка, такую как тип цикла или способы передачи параметров в функции и т. Д., И обычную функциональность, которая нужна большинству приложений.
Библиотеки - это функции, которые могут быть полезны для многих программистов, поэтому они создаются как код многократного использования, которым можно делиться. Стандартные библиотеки предназначены для очень распространенных функций, которые обычно нужны программистам. Таким образом, язык программирования сразу полезен для более широкого круга программистов. Библиотеки могут быть обновлены и расширены без изменения основных функций самого языка.
PHP
в качестве примера вряд ли можно различить его обширные языковые функции и сам язык.
include
, require
и require_once
, если / для / While (структурного программирования), исключение, отдельная система «значений ошибок», осложненная слабых правила типизации, осложненное правило оператора старшинства, и так далее , Сравните это с простотой, скажем, Smalltalk, Scheme, Prolog, Forth и т.
В дополнение к тому, что уже сказано в других ответах, введение стандартных функций в библиотеку - это разделение задач :
Работа компилятора - анализировать язык и генерировать для него код. Задача компилятора - не содержать ничего, что уже может быть написано на этом языке и предоставлено в виде библиотеки.
Это стандартная библиотека (которая всегда доступна неявно) для обеспечения основной функциональности, которая нужна практически всем программам. Это не работа стандартной библиотеки - содержать все функции, которые могут быть полезны.
Задача дополнительных стандартных библиотек заключается в предоставлении вспомогательных функций, без которых многие программы могут обходиться, но которые все еще довольно просты, а также необходимы для многих приложений, чтобы гарантировать доставку в стандартных средах. Задача этих дополнительных библиотек - не содержать весь повторно используемый код, который когда-либо был написан.
Работа пользовательских библиотек заключается в предоставлении коллекций полезных многократно используемых функций. Это не работа пользовательских библиотек - содержать весь код, который когда-либо был написан.
Работа исходного кода приложения состоит в том, чтобы предоставить оставшиеся фрагменты кода, которые действительно имеют отношение только к этому одному приложению.
Если вам нужно универсальное программное обеспечение, вы получаете что-то невероятно сложное. Вам нужно выполнить модульность, чтобы снизить сложность до управляемых уровней. И вам нужно модулировать, чтобы разрешить частичные реализации:
Библиотека потоков бесполезна на одноядерном встроенном контроллере. Разрешение языковой реализации для этого встроенного контроллера просто не включать pthread
библиотеку - это просто правильная вещь.
Математическая библиотека бесполезна на микроконтроллере, который даже не имеет FPU. Опять же, отсутствие принуждения к предоставлению таких функций значительно sin()
облегчает жизнь разработчикам вашего языка для этого микроконтроллера.
Даже базовая стандартная библиотека ничего не стоит, когда вы программируете ядро. Вы не можете реализовать write()
без системного вызова в ядре, и вы не можете реализовать printf()
без write()
. Как программист ядра, ваша задача - предоставить write()
системный вызов, вы не можете просто ожидать, что он будет там.
Язык, который не допускает таких пропусков в стандартных библиотеках , просто не подходит для многих задач . Если вы хотите, чтобы ваш язык мог гибко использоваться в необычных средах, он должен быть гибким в том, что входит в стандартные библиотеки. Чем больше ваш язык опирается на стандартные библиотеки, тем больше допущений он делает в своей среде выполнения и тем самым ограничивает его использование средами, которые предоставляют эти предварительные условия.
Конечно, языки высокого уровня, такие как python и java, могут сделать много предположений относительно своей среды. И они имеют тенденцию включать много, много вещей в свои стандартные библиотеки. Языки более низкого уровня, такие как C, предоставляют гораздо меньше в своих стандартных библиотеках и значительно уменьшают базовую стандартную библиотеку. Вот почему вы найдете работающий компилятор C практически для любой архитектуры, но, возможно, не сможете запустить на нем никаких сценариев Python.
Одна большая причина, по которой компиляторы и стандартные библиотеки разделены, заключается в том, что они служат двум различным целям (даже если они оба определены в одной и той же языковой спецификации): компилятор преобразует высокоуровневый код в машинные инструкции, а стандартная библиотека предоставляет предварительно протестированные реализации часто используемых функций. Авторы компиляторов ценят модульность так же, как и другие разработчики программного обеспечения. Фактически, некоторые из ранних C-компиляторов дополнительно разделяют компилятор на отдельные программы для предварительной обработки, компиляции и компоновки.
Эта модульность дает вам ряд преимуществ:
Исторически говоря (по крайней мере, с точки зрения C), исходные версии языка до стандартизации вообще не имели стандартной библиотеки. Поставщики ОС и третьи стороны часто предоставляют библиотеки, полные обычно используемых функций, но разные реализации включают разные вещи, и они в значительной степени несовместимы друг с другом. Когда C был стандартизирован, они определили «стандартную библиотеку» в попытке согласовать эти несопоставимые реализации и улучшить переносимость. Стандартная библиотека C разработана отдельно от языка, как библиотеки Boost для C ++, но позже была интегрирована в спецификацию языка.
Дополнительный ответ: «Интеллектуальная собственность»
Примечательным примером является реализация Math.Pow (double, double) в .NET Framework, который был приобретен Microsoft у Intel и остается нераскрытым, даже если среда стала открытой. (Если быть точным, в приведенном выше случае это внутренний вызов, а не библиотека, но идея верна). Библиотека, отделенная от самого языка (теоретически также подмножество стандартных библиотек), может дать сторонникам языка больше гибкости при рисовании грань между тем, что должно быть прозрачным, и тем, что должно оставаться нераскрытым (из-за их договоров с третьими сторонами или по другим причинам, связанным с ИС)
Math.Pow
, не упоминается ни о покупке, ни о Intel, и рассказывается о людях, читающих исходный код реализации функции.
Ошибки и отладка.
Ошибки: все программное обеспечение содержит ошибки, ваша стандартная библиотека содержит ошибки, а ваш компилятор содержит ошибки. Как пользователь языка, гораздо легче находить и обходить такие ошибки, когда они находятся в стандартной библиотеке, а не в компиляторе.
Отладка: мне намного легче увидеть трассировку стека стандартной библиотеки и дать мне некоторое представление о том, что может пойти не так. Потому что у этой трассировки стека есть код, который я понимаю. Конечно, вы можете копать глубже и отслеживать внутренние функции, но намного проще, если вы используете язык, который вы используете постоянно изо дня в день.
Это отличный вопрос!
Стандарт C ++, например, никогда не указывает, что должно быть реализовано в компиляторе или в стандартной библиотеке: он просто ссылается на реализацию . Например, зарезервированные символы определяются как компилятором (как встроенные), так и стандартной библиотекой, взаимозаменяемо.
Тем не менее, все реализации C ++, о которых я знаю, будут иметь минимально возможное количество встроенных функций, предоставляемых компилятором, и в максимально возможной степени предоставляемых стандартной библиотекой.
Таким образом, хотя технически выполнимо определить стандартную библиотеку как встроенную функциональность в компиляторе, на практике она редко используется.
Давайте рассмотрим идею переноса некоторой части функциональности из стандартной библиотеки в компилятор.
Преимущества:
Недостатки:
std
) для экспериментов.Это означает, что перемещение чего-либо в компилятор является дорогостоящим , сейчас и в будущем, и, следовательно, требует серьезного обоснования. Для некоторых частей функциональности это необходимо (они не могут быть написаны как обычный код), однако даже тогда стоит извлекать минимальные и общие части, чтобы перейти к компилятору и построить поверх них в стандартной библиотеке.
Как сам языковой дизайнер, я хотел бы повторить некоторые другие ответы здесь, но предоставить их глазами кого-то, кто создает язык.
API не закончен, когда вы закончите добавлять все, что можете в него. API заканчивается, когда вы берете все, что можете из него.
Язык программирования должен быть указан с использованием некоторого языка. Вы должны быть в состоянии передать значение любой программы, написанной на вашем языке. На этом языке очень трудно писать, и еще труднее писать хорошо. В целом, это, как правило, очень точная и хорошо структурированная форма английского языка, используемая для передачи значения не компьютеру, а другим разработчикам, особенно тем разработчикам, которые пишут компиляторы или интерпретаторы для вашего языка. Вот пример из спецификации C ++ 11, [intro.multithread / 14]:
Видимая последовательность побочных эффектов на атомарном объекте M в отношении вычисления значения B для M представляет собой максимальную непрерывную подпоследовательность побочных эффектов в порядке модификации M, где первый побочный эффект виден относительно B и для каждого побочного эффекта, это не тот случай, когда B происходит до него. Значение атомарного объекта M, как определено оценкой B, должно быть значением, сохраненным некоторой операцией в видимой последовательности M относительно B. [Примечание: можно показать, что видимая последовательность побочных эффектов значения вычисления являются уникальными, учитывая требования согласованности ниже. —Конечная записка]
Блек! Любой, кто попробовал понять, как C ++ 11 обрабатывает многопоточность, может понять, почему формулировка должна быть настолько непрозрачной, но это не прощает того факта, что она ... ну ... такая непрозрачная!
Сравните это с определением std::shared_ptr<T>::reset
в разделе библиотеки стандарта:
template <class Y> void reset(Y* p);
Эффекты: эквивалентно
shared_ptr(p).swap(*this)
Так в чем же разница? В части определения языка авторы не могут предполагать, что читатель понимает языковые примитивы. Все должно быть тщательно указано в английской прозе. Как только мы дойдем до части определения библиотеки, мы можем использовать язык для определения поведения. Это часто намного проще!
В принципе, можно получить плавную компоновку из примитивов в начале спецификации, вплоть до определения того, что мы считаем «стандартными библиотечными функциями», без необходимости проводить черту между «языковыми примитивами» и функции "стандартной библиотеки". На практике эту линию чрезвычайно полезно рисовать, поскольку она позволяет вам писать некоторые из самых сложных частей языка (например, те, которые должны реализовывать алгоритмы), используя язык, предназначенный для их выражения.
И мы действительно видим некоторые размытые линии:
java.lang.ref.Reference<T>
может только быть подклассы по стандартной библиотеки классов java.lang.ref.WeakReference<T>
java.lang.ref.SoftReference<T>
и java.lang.ref.PhantomReference<T>
потому , что поведение Reference
так глубоко переплетены со спецификацией языка Java , что им нужно , чтобы поставить некоторые ограничения в части этого процесса реализуется как «стандартная библиотека» классов.Это подразумевается как дополнение к существующим ответам (и это слишком долго для комментария).
Есть как минимум две другие причины для стандартной библиотеки:
Если какая-то особенность языка есть в библиотечной функции, и я хочу знать, как она работает, я могу просто прочитать исходный текст этой функции. Если я хочу отправить отчет об ошибке / патч / запрос на извлечение, обычно не сложно написать код для исправления и тестовых примеров. Если это в компиляторе, я должен быть в состоянии копаться во внутренностях. Даже если он написан на одном языке (и так должно быть, любой уважающий себя компилятор должен быть размещен самостоятельно), код компилятора не похож на код приложения. Может потребоваться вечность, чтобы даже найти правильные файлы.
Если вы идете по этому пути, вы отсекаете себя от множества потенциальных участников.
Многие языки предлагают эту функцию в той или иной степени, но было бы чрезвычайно сложно перезагрузить код, который выполняет горячую перезагрузку. Если SL отдельно от среды выполнения, его можно перезагрузить.
Это интересный вопрос, но уже дано много хороших ответов, поэтому я не буду пытаться дать полный.
Тем не менее, две вещи, которые я не думаю, получили достаточно внимания:
Во-первых, все это не супер четко. Это что-то вроде спектра, потому что есть причины действовать по-другому. Например, компиляторы часто знают о стандартных библиотеках и их функциях. Пример примера: функция «Hello World» на C - printf - лучшая из тех, что я могу себе представить. Это библиотечная функция, вроде как, так как она очень зависит от платформы. Но это поведение (определяемое реализацией) должно быть известно компилятору, чтобы предупредить программиста о неправильных вызовах. Это не особенно опрятно, но было замечено как хороший компромисс. Кстати, это реальный ответ на большинство вопросов «почему этот дизайн»: много компромиссов и «в то время казалось хорошей идеей». Не всегда "это был ясный способ сделать это" или "
Второе - это то, что она позволяет стандартной библиотеке не быть такой стандартной. Существует множество ситуаций, когда язык желателен, но стандартные библиотеки, которые его обычно сопровождают, не являются ни практичными, ни желательными. Это чаще всего происходит с языками системного программирования, такими как C, на нестандартных платформах. Например, если у вас есть система без ОС или планировщика: у вас не будет потоков.
Со стандартной моделью библиотеки (и поддержкой потоков в ней) это можно сделать аккуратно: компилятор почти такой же, вы можете повторно использовать биты применяемых библиотек и все, что не можете удалить. Если это запекается в компиляторе, вещи начинают запутываться.
Например:
Вы не можете быть совместимым компилятором.
Как бы вы указали свое отклонение от стандарта. Обратите внимание, что обычно существует какая-то форма синтаксиса импорта / включения, которая может иметь сбой, например импорт Питона или включение С, которое легко указывает на проблему, если в стандартной модели библиотеки чего-то не хватает.
Аналогичные проблемы возникают, если вы хотите настроить или расширить функциональность «библиотеки». Это гораздо чаще, чем вы думаете. Просто придерживайтесь многопоточности: Windows, Linux и некоторые экзотические сетевые процессоры все делают потоки совершенно по-разному. Хотя биты linux / windows могут быть довольно статичными и могут использовать идентичный API, материал NPU будет меняться в зависимости от дня недели и API с ним. Компиляторы быстро отклонятся, когда люди решат, какие биты они должны поддерживать / могли бы делать с ними достаточно быстро, если бы не было способа разделить подобные вещи.