Чому стандартні бібліотеки не є програмуванням мови примітивів? [зачинено]


30

Я думав, чому існують (на всіх мовах програмування, які я вивчив, наприклад, C ++, Java, Python) стандартні бібліотеки, такі як stdlib, замість того, щоб подібні "функції" були примітивом самої мови.


4
Що ви маєте на увазі "чому компілятор не міг просто перевести виклик функції в набір інструкцій"? Це приблизно те, що робить компілятор, стандартна бібліотека чи ні (Гаразд, Python тільки partway та Java-JVM bytecode; аналогічна концепція). Стандартні бібліотеки насправді не мають нічого спільного з компіляцією коду -> інструкцій.
Delioth

25
@Delioth Я думаю, що Сімоне запитує, чому все не в стандартній бібліотеці мови $ LANG замість цього примітивна конструкція / функція цієї мови. Я б сказав, що це розумне запитання для тих, хто дуже новачок у мовах програмування :)
Андрес Ф.

33
Стандартна бібліотека зазвичай заповнює розрив між робочою мовою програмування та корисною, якою користуватимуться люди.
Теластин

6
Значна частина стандартної бібліотеки Python насправді написана на С та вже складена.
ElmoVanKielmo

1
На противагу цьому, у більшості реалізацій BASIC все є частиною мови, а бібліотек взагалі немає і не підтримує їх (крім кількох реалізацій, для можливості виклику в машинних мовних процедурах).
Євро Міцеллі

Відповіді:


32

Дозвольте мені трохи розширитись на гарну відповідь @ Vincent (+1) :

Чому компілятор не міг просто перевести виклик функції в набір інструкцій?

Це може, і це робиться за допомогою щонайменше двох механізмів:

  • вбудований виклик функції - під час перекладу компілятор може замінити виклик вихідного коду з його реалізацією безпосередньо вбудованим, а не робити фактичний виклик функції. Тим не менш, функція повинна мати десь визначену реалізацію, яка може бути в стандартній бібліотеці.

  • внутрішня функція - intrinsics - це функції, про які було повідомлено компілятору, не обов'язково знаходячи функцію в бібліотеці. Зазвичай вони зарезервовані для апаратних функцій, які практично не доступні будь-яким іншим способом, будучи настільки простими, що навіть накладні витрати на функцію бібліотеки мови збірки вважаються високими. (Компілятор, як правило, лише автоматично вбудовує вихідний код своєю мовою, але не функції складання, саме там вбудований внутрішній механізм.)

Проте, як говориться, найкращим варіантом іноді є компілятор перевести виклик функції з мови джерела у функційний виклик у машинному коді. Рекурсія, віртуальні методи та розмір - це деякі причини того, що вбудовування не завжди є можливим / практичним. (Ще одна причина - це наміри збірки, такі як окрема компіляція (об'єктні модулі), окремі блоки завантаження (наприклад, DLL).

Немає реальної переваги і в тому, щоб зробити більш стандартні функції бібліотеки ітрісікою (це б важко кодувати набагато більше знань у компіляторі без реальної переваги), тому повторний виклик машинного коду часто є найбільш підходящим.

C - помітна мова, яка, мабуть, опустила інші явні мовні висловлювання на користь стандартних функцій бібліотеки. Незважаючи на те, що бібліотеки існували раніше, ця мова змінилася на більшу роботу від стандартних функцій бібліотеки і менше як явні висловлювання в граматиці мови. Наприклад, IO в інших мовах часто давали власний синтаксис у вигляді різних висловлювань, тоді як граматика C не визначає жодних операцій вводу-виводу, а натомість відкладається до своєї стандартної бібліотеки, щоб забезпечити це всім доступним через виклики функцій, які компілятор вже знає, як це зробити.


3
Гарна відповідь. Слід додати кілька слів, чому було прийнято таке рішення в C: якщо я правильно пам’ятаю, головна причина була насправді в тому, що це спростило створення компіляторів C для багатьох різних апаратних архітектур.
Док Браун

10
@DocBrown До 1975 року було достатньо прикладів у галузі розробки мови програмування (ALGOL-68, хтось?), Який показав, що спроби вкласти все в мову безпосередньо призвели до значних уповільнень у обох виправленнях мовних специфікацій та у створенні мовних реалізацій.
Joker_vD

5
Подібний приклад - це те, що робив Python print: У 2.x це було висловлювання зі своєю спеціальною граматикою, але в 3.x це стало лише черговим викликом функції. Дивіться PEP 3105 для офіційного пояснення.
dan04

1
@DocBrown, портативність майже точно не є причиною. Коли були створені Unix і C, вони були спроектовані та побудовані саме для однієї машини - запасного PDP-7, оскільки Кен Томпсон замислився над тим, які концепції можна врятувати від невдалого проекту Multics. C також був створений з однієї причини: мати мову високого рівня, на якій (повторно) реалізувати Unix. Ми в основному експеримент в розробці програмного забезпечення, а не серйозна спроба комерційної багатоплатформової ОС та мови. Дивіться, наприклад, bell-labs.com/usr/dmr/www/chist.html .
Євро Міцеллі

@EuroMicelli: Я не бачу суперечності. А ваша довідка містить багато деталей про те, коли портативність набула важливого значення, це було насправді в перші роки розвитку C та Unix. Тут я можу тільки здогадуватися, але якби винахідники С не зберегли б мову навмисно мало, я думаю, це було б цілком малоймовірно, щоб вони могли перенести її так швидко і успішно для багатьох різних архітектур.
Док Браун

70

Це просто, щоб зберегти саму мову якомога простіше. Потрібно розрізняти особливості мови, наприклад тип циклу або способи передачі параметрів функцій тощо, і загальну функціональність, яка потрібна більшості програм.

Бібліотеки - це функції, які можуть бути корисні багатьом програмістам, тому вони створюються як багаторазовий код, яким можна ділитися. Стандартні бібліотеки розроблені як дуже поширені функції, які програмістам зазвичай потрібні. Таким чином мова програмування стає негайно корисною для більш широкого кола програмістів. Бібліотеки можна оновлювати та розширювати, не змінюючи основних особливостей самої мови.


3
Не завжди. PHPяк приклад навряд чи робить різниці між його величезними мовними функціями та самою мовою.
Вахід Амірі

15
Я б не брав PHP як приклад простої мови
DrBreakalot

3
@DrBreakalot PHP - надзвичайно проста мова. Це не означає, що він має послідовний дизайн, але це інше питання.
Гонки легкості з Монікою

19
@LightnessRacesinOrbit Я взагалі би не назвав PHP "простим": у нього об'єктна система на основі класу, окремий набір "примітивних значень", автономні функції, першокласні закриття, побудовані на об'єктній системі, механізм простору імен, різні поняття званих «статичний», висловлювання, а також висловлювання, include, requireі require_once, якщо / для / While (структурного програмування), виняток, окрема система «значень помилок», ускладнена слабких правила типізації, ускладнене правило оператора старшинства, і так далі . Порівняйте це з простотою, скажімо, Smalltalk,
Schema

3
Основна причина, на яку натякають, але прямо не вказана у цій відповіді, полягає в тому, що зберігаючи мову якомога простішою, це набагато простіше реалізувати на інших платформах. Оскільки стандартні бібліотеки, як правило, написані на самій мові , їх можна тривіально переносити.
BlueRaja - Danny Pflughoeft

34

Окрім того, що вже відповіли інші відповіді, включення стандартних функцій у бібліотеку - це розділення проблем :

  • Завдання компілятора розбирати мову та генерувати код для неї. Не завдання компілятора містити все, що вже може бути написано цією мовою та надано як бібліотека.

  • Це стандартна робота бібліотеки (та, яка завжди неявно доступна), яка забезпечує основну функціональність, необхідну практично всім програмам. Це не стандартна робота бібліотеки, щоб містити всі функції, які можуть бути корисні.

  • Завданням додаткових стандартних бібліотек є надання допоміжних функціональних можливостей, без яких може працювати безліч програм, але вони все ще досить основні, а також важливі для багатьох додатків, щоб гарантувати доставку в стандартних умовах. Це не обов'язок цих необов'язкових бібліотек містити весь код багаторазового використання, який коли-небудь написаний.

  • Завдання бібліотек користувачів - надавати колекції корисних функцій багаторазового використання. Це не завдання бібліотек користувачів містити весь код, який коли-небудь написаний.

  • Завдання вихідного коду програми - надання решти бітів коду, які дійсно стосуються лише однієї програми.

Якщо ви хочете, щоб програмне забезпечення було розміщене для всіх, ви отримаєте щось шалено складне. Вам потрібно модулювати, щоб знизити складність до керованих рівнів. І вам потрібно модулювати, щоб дозволити часткові реалізації:

  • На одноядерному вбудованому контролері бібліотека ниток не має нічого. Дозвіл мовної реалізації цього вбудованого контролера просто не включати pthreadбібліотеку - це правильно.

  • Математична бібліотека не потрібна на мікроконтролері, який навіть не має FPU. Знову ж таки, не змушуючи надавати такі функції, як sin()життя набагато простіше для впроваджувачів вашої мови для цього мікроконтролера.

  • Навіть основна стандартна бібліотека марна, коли ви програмуєте ядро. Ви не можете реалізувати write()без syscall в ядрі, і ви не можете реалізувати printf()без нього write(). Як програміст ядра, ваша робота - забезпечити write()системний виклик, ви не можете просто очікувати, що він буде там.

Мова, яка не допускає таких пропусків у стандартних бібліотеках , просто не підходить для багатьох завдань . Якщо ви хочете, щоб ваша мова була гнучкою у використанні в незвичних умовах, вона повинна бути гнучкою в тому, які стандартні бібліотеки включені. Чим більше ваша мова покладається на стандартні бібліотеки, тим більше припущень робить її середовище виконання, а отже, обмежує її використання середовищами, які забезпечують ці передумови.

Звичайно, мови високого рівня, такі як python та java, можуть зробити багато припущень щодо свого оточення. І вони, як правило, включають багато, багато речей у свої стандартні бібліотеки. Мови нижчого рівня, такі як C, набагато менше забезпечують у своїх стандартних бібліотеках, а основна стандартна бібліотека - значно меншою. Ось чому ви знайдете працюючий компілятор C практично для будь-якої архітектури, але, можливо, не зможете запустити на ньому жоден сценарій python.


16

Одна з великих причин компіляторів і стандартних бібліотек є окремими, оскільки вони служать двом різним цілям (навіть якщо вони обидва визначені однією і тією ж специфікацією мови): компілятор переводить код вищого рівня в інструкції до машин, а стандартна бібліотека забезпечує попередньо перевірені реалізація загально потрібної функціональності. Автори компілятора цінують модульність так само, як це роблять інші розробники програмного забезпечення. Насправді, деякі з ранніх компіляторів C далі розбивають компілятор на окремі програми для попередньої обробки, компіляції та зв’язування.

Ця модульність дає безліч переваг:

  • Це мінімізує обсяг роботи, необхідний при підтримці нової апаратної платформи, оскільки більша частина стандартного коду бібліотеки є апаратно-агностичним, можна повторно використовувати.
  • Стандартну реалізацію бібліотеки можна оптимізувати різними способами (для швидкості, простору, використання ресурсів тощо). У багатьох ранніх обчислювальних системах був доступний лише один компілятор, і наявність окремої стандартної бібліотеки означала, що розробники можуть міняти реалізацію відповідно до своїх потреб.
  • Стандартна функціональність бібліотеки навіть не повинна існувати. Наприклад, при написанні коду голого металу C ви маєте повнофункціональний компілятор, але більшість стандартних функцій бібліотеки відсутня, і деякі речі, такі як введення / виведення файлів, навіть неможливі. Якщо для втілення цієї функції потрібен був компілятор, ви не могли б мати компілятор C, що відповідає стандартам, на деяких платформах, де вам це найбільше потрібно.
  • На ранніх системах компілятори часто розроблялися компанією, яка розробляла обладнання. Стандартні бібліотеки часто надавалися постачальником ОС, оскільки їм часто потрібен доступ до функціональних можливостей (наприклад, системних дзвінків), характерних для цієї програмної платформи. Письменнику-компілятору було недоцільно підтримувати всі різні комбінації апаратного та програмного забезпечення (раніше було набагато більше різноманітності як в архітектурі апаратних засобів, так і в платформі програмного забезпечення).
  • У мовах високого рівня стандартна бібліотека може бути реалізована як динамічно завантажена бібліотека. Потім одна стандартна реалізація бібліотеки може використовуватися декількома компіляторами та / або мовами програмування.

Історично кажучи (принаймні з точки зору С), оригінальні, передстандартні версії мови взагалі не мали стандартної бібліотеки. Постачальники ОС та треті сторони часто надають бібліотеки, що мають повний загальнозміцнений функціонал, але різні реалізації включали різні речі, і вони значною мірою були несумісними один з одним. Коли C було стандартизовано, вони визначили "стандартну бібліотеку", намагаючись гармонізувати ці розрізнені реалізації та покращити портативність. Стандартна бібліотека С розроблена окремо від мови, як і бібліотеки Boost для C ++, але згодом була інтегрована в мовну специфікацію.


6

Додаткова відповідь у прямому випадку: управління інтелектуальною власністю

Примітним прикладом є реалізація Math.Pow (подвійний, подвійний) у .NET Framework, який Microsoft придбав від Intel і залишається нерозкритим, навіть якщо рамки виходили з відкритим кодом. (Якщо бути точним, у вищенаведеному випадку це внутрішній виклик, а не бібліотека, але ідея справедлива.) Бібліотека, відокремлена від самої мови (теоретично також підмножина стандартних бібліотек), може дати мовним мовникам більше гнучкості в малюванні малюнка між тим, що слід тримати прозорим, і тим, що повинно залишатися нерозголошеним (через їх договори з третіми сторонами чи інші причини, пов'язані з ІС).


Це заплутано. Сторінка, на яку ви посилаєтесь Math.Pow, не згадує жодної покупки чи нічого про Intel та розповідає про людей, які читають вихідний код реалізації функції.
Гонки легкості з Монікою

@LightnessRacesinOrbit - хм, я все ще можу його побачити там (при пошуку "intel"). Ви також можете знайти посилання на останній вихідний код (в останніх коментарях), а також альтернативну реалізацію (у другій відповіді), яка є загальнодоступною, але її складність та коментована неефективність дозволяють підказати, чому оригінальна реалізація все ще не розкрита. Дійсно ефективна реалізація може зажадати глибоких знань про багато деталей на рівні процесора, які не обов'язково доступні у відкритому доступі.
miroxlav

5

Помилки та налагодження.

Помилки: у всіх програмних продуктах є помилки, у вашій стандартній бібліотеці є помилки, а у вашому компіляторі - помилки. Як користувач мови, набагато простіше знайти та вирішити такі помилки, коли вони знаходяться в стандартній бібліотеці, на відміну від компілятора.

Налагодження: мені набагато простіше бачити слід стека стандартної бібліотеки і давати мені певне уявлення про те, що може піти не так. Тому що в цьому сліді стека є код, який я розумію. Звичайно, ви можете копати глибше, а також можете простежити свої внутрішні функції, але це набагато простіше, якщо це мовою, якою ви користуєтесь весь день з дня на день.


5

Це відмінне запитання!

Витвір мистецтва

Наприклад, стандарт C ++ ніколи не вказує, що слід реалізувати в компіляторі або в стандартній бібліотеці: він просто стосується реалізації . Наприклад, зарезервовані символи визначаються як компілятором (як внутрішні символи), так і стандартною бібліотекою взаємозамінно.

Тим не менш, усі C ++ реалізації, про які я знаю, матимуть мінімально можливу кількість внутрішніх даних, що надаються компілятором, і максимально забезпечені стандартною бібліотекою.

Таким чином, хоча технічно доцільно визначити стандартну бібліотеку як внутрішню функціональність у компіляторі, на практиці це здається рідко використаним.

Чому?

Розглянемо ідею переміщення певного фрагмента функціональності зі стандартної бібліотеки до компілятора.

Переваги:

  • Краща діагностика: властивості можуть бути спеціалізованими.
  • Краща продуктивність: внутрішні тексти можуть бути спеціалізованими.

Недоліки:

  • Збільшена маса компілятора: кожен окремий випадок додає складності компілятору; складність збільшує витрати на обслуговування та ймовірність появи помилок.
  • Повільна ітерація: зміна реалізації функціоналу вимагає зміни самого компілятора, що ускладнює створення лише невеликої бібліотеки (поза межами std) для експерименту.
  • Більш висока смуга для входу: чим дорожче / складніше щось змінити, тим менше людей, швидше за все, вскочать.

Це означає, що перенести щось на компілятор дорого , зараз і в майбутньому, і тому це вимагає грунтовного випадку. Для деяких фрагментів функціоналу це необхідно (вони не можуть бути записані як звичайний код), однак навіть тоді виплачується витягнути мінімальні та загальні фрагменти, щоб перейти до компілятора та створити їх у стандартній бібліотеці.


5

Як сам мовний дизайнер, я хотів би повторити деякі інші відповіді, але надати це очима того, хто будує мову.

API не закінчено, коли ви закінчите додавати в нього все, що можете. API закінчено, коли ви закінчите, і все, що ви можете, з нього ви можете отримати.

Мова програмування має бути визначена за допомогою якоїсь мови. Ви повинні мати можливість передати значення, що стоїть за будь-якою програмою, написаною вашою мовою. Цю мову дуже важко написати, а ще важче добре написати. Взагалі, це, як правило, дуже точна та добре структурована форма англійської мови, яка використовується для передачі сенсу не для комп'ютера, а для інших розробників, особливо тих розробників, які пишуть компілятори чи перекладачі для вашої мови. Ось приклад із специфікації C ++ 11, [intro.multithread / 14]:

Видима послідовність побічних ефектів на атомному об'єкті M щодо обчислення значення B М є максимальною суміжною підпослідовністю побічних ефектів у порядку модифікації 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, java.lang.ref.Reference<T>може тільки бути підкласи по стандартної бібліотеки класів java.lang.ref.WeakReference<T> java.lang.ref.SoftReference<T>і java.lang.ref.PhantomReference<T>тому , що поведінка Referenceтак глибоко переплетені зі специфікацією мови Java , що їм потрібно , щоб поставити деякі обмеження в частині цього процесу реалізується як «стандартна бібліотека» класів.
  • У C # є клас System.Delegate, який інкапсулює поняття делегатів. Незважаючи на свою назву, він не є делегатом. Це також абстрактний клас (не може бути примірник), з якого ви не можете створити похідні класи. Тільки система може це зробити за допомогою функцій, записаних у специфікації мови.

2

Це розуміється як доповнення до існуючих відповідей (і занадто довго для коментарів).

Існує щонайменше дві інші причини для стандартної бібліотеки:

Перешкода для вступу

Якщо певна мова є функцією бібліотеки, і я хочу знати, як вона працює, я можу просто прочитати джерело для цієї функції. Якщо я хочу надіслати звіт про помилку / патч / витягнути запит, кодувати виправлення та тестові справи, як правило, не так вже й складно. Якщо він знаходиться в компіляторі, я повинен мати можливість копатися у внутрішній. Навіть якщо він є тією ж мовою (і це має бути, будь-який поважаючий себе компілятор повинен бути влаштованим) код компілятора - це не що інше, як код програми. Навіть знайти правильні файли може вічно.

Якщо ви йдете цим маршрутом, ви позбавитесь від багатьох потенційних учасників.

Завантаження гарячого коду

Багато мов пропонують цю функцію в тій чи іншій мірі, але було б надзвичайно складно перезавантажити код, який виконує гаряче перезавантаження. Якщо SL є окремим від часу виконання, він може бути перезавантажений.


3
"будь-який поважаючий себе компілятор повинен бути влаштованим" - зовсім не. Було б безглуздо мати версії скажімо LLVM, написані на C, C ++, Objective-C, Swift, Fortran і так далі, щоб компілювати всі ці мови.
gnasher729

@ gnasher729 це не певний випадок (поряд з іншими багатомовними цілями, такими як CLR)?
Джаред Сміт

@JaredSmith Я б сказав, що це зараз загальна справа, а не особлива. Ніхто вже не пише "компілятор" як монолітну програму. Натомість вони виробляють компіляторні системи . Більшість функціональних можливостей повного компілятора повністю не залежить від конкретної мови, яка складається, і велика частина мови, що залежить від мови, може бути виконана шляхом надання різних даних, що визначають граматику мови, а не шляхом написання різного коду для кожної мови хочу скласти.
алефзеро

2

Це цікаве запитання, але вже дано багато хороших відповідей, тому я не буду намагатися повного.

Однак дві речі, які, на мою думку, не приділили достатньої уваги:

По-перше, все це не надто чітко. Це трохи спектр саме тому, що є причини робити щось інакше. Наприклад, компілятори часто знають про стандартні бібліотеки та їх функції. Приклад прикладу: Функція "Hello World" C - printf - найкраща, про яку я можу придумати. Це функція бібліотеки, такою має бути, оскільки це дуже залежить від платформи. Але його поведінку (визначення визначено) необхідно знати компілятору, щоб попередити програміста про погані виклики. Це не особливо охайно, але сприймалося як хороший компроміс. Між іншим, це справжня відповідь на більшість питань "чому цей дизайн": багато компромісів і "здавалося, як хороша ідея в той час". Не завжди "це був чіткий спосіб зробити це" або "

По-друге, це дозволяє стандартній бібліотеці не бути всім, що стандарт. Існує багато ситуацій, коли мова є бажаною, але стандартні бібліотеки, які зазвичай супроводжують їх, не є і практичними, і бажаними. Найчастіше це стосується системних мов програмування, таких як C, на нестандартних платформах. Наприклад, якщо у вас є система без ОС або планувальник: ви не будете мати нитки.

За допомогою стандартної моделі бібліотеки (і в ній підтримується нитка) це можна обробляти чисто: компілятор майже такий же, ви можете повторно використовувати біти бібліотек, які застосовуються, і все, що не зможете видалити. Якщо це запікається в компілятор, все починає псуватися.

Наприклад:

  • Ви не можете бути сумісним компілятором.

  • Як би ви вказали своє відхилення від стандарту. Зауважте, що зазвичай є якась форма імпорту / включення синтаксису, у вас може виникнути помилка, тобто імпорт пітонів або C, що легко вказує на проблему, якщо в стандартній моделі бібліотеки чогось не вистачає.

Також подібні проблеми виникають, якщо ви хочете налаштувати або розширити функціональність "бібліотеки". Це набагато частіше, ніж можна подумати. Просто дотримуйтесь нитки: windows, linux та деякі екзотичні мережеві опрацювальні блоки все це робить різьблення зовсім інакше. Хоча біти Linux / Windows можуть бути досить статичними і матимуть змогу використовувати ідентичний API, матеріали NPU змінюватимуться з днем ​​тижня, а API з ним. Компілятори швидко відхиляться, оскільки люди вирішують, які біти вони повинні підтримувати / могли б робити з нею досить швидко, якби не було можливості поділити подібну річ.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.