web-dev-qa-db-fra.com

Pourquoi les bibliothèques standard ne sont-elles pas des primitives de langage de programmation?

Je pensais pourquoi existe-t-il (dans tous les langages de programmation que j'ai appris, comme C++, Java, Python) des bibliothèques standard comme stdlib, au lieu d'avoir des "fonctions" similaires étant une primitive du langage lui-même.

32
user327143

Permettez-moi de développer quelque peu @ Vincent's (+1) bonne réponse :

Pourquoi le compilateur ne pouvait-il pas simplement traduire un appel de fonction en un ensemble d'instructions?

Il peut et le fait via au moins deux mécanismes:

  • en insérant un appel de fonction - pendant la traduction, le compilateur peut remplacer un appel de code source avec son implémentation directement en ligne au lieu de faire un appel réel à la fonction. La fonction doit toujours avoir une implémentation définie quelque part et qui peut être dans la bibliothèque standard.

  • fonction intrinsèque - les intrinsèques sont des fonctions dont le compilateur a été informé sans nécessairement trouver la fonction dans une bibliothèque. Celles-ci sont généralement réservées aux fonctionnalités matérielles qui ne sont pratiquement accessibles d'aucune autre manière, étant si simples que même la surcharge d'un appel à la fonction de bibliothèque de langage d'assemblage est considérée comme élevée. (Le compilateur ne peut généralement incorporer automatiquement que du code source dans son langage, mais pas les fonctions d'assemblage, où intervient le mécanisme intrinsèque.)

Cela étant dit, la meilleure option est parfois pour le compilateur de traduire un appel de fonction dans le langage source en un appel de fonction dans le code machine. La récursivité, les méthodes virtuelles et la taille pure sont quelques raisons pour lesquelles l'inline n'est pas toujours possible/pratique. (Une autre raison est l'intention de la construction, comme la compilation séparée (modules d'objet), les unités de charge distinctes (par exemple les DLL)).

Il n'y a pas non plus d'avantage réel à rendre intrisiques la plupart des fonctions de bibliothèque standard (cela coderait en dur beaucoup plus de connaissances dans le compilateur pour aucun avantage réel), donc un appel de code machine à nouveau est souvent le plus approprié.

C est un langage notable qui a sans doute omis d'autres déclarations de langage explicites en faveur des fonctions de bibliothèque standard. Bien que les bibliothèques préexistaient, ce langage a changé pour faire plus de travail à partir des fonctions de bibliothèque standard et moins en tant qu'énoncés explicites dans la grammaire du langage. IO dans d'autres langues, par exemple, a souvent reçu sa propre syntaxe sous la forme de diverses instructions, tandis que la grammaire C ne définit aucune instruction IO, au lieu de cela, il suffit de se reporter à sa bibliothèque standard pour le fournir, accessible via des appels de fonction, ce que le compilateur sait déjà faire.

31
Erik Eidt

Il s'agit simplement de garder la langue elle-même aussi simple que possible. Vous devez faire la distinction entre une caractéristique du langage, comme un type de boucle ou des façons de transmettre des paramètres aux fonctions, etc., et les fonctionnalités courantes dont la plupart des applications ont besoin.

Les bibliothèques sont des fonctions qui peuvent être utiles à de nombreux programmeurs, elles sont donc créées sous forme de code réutilisable qui peut être partagé. Les bibliothèques standard sont conçues pour être des fonctions très courantes dont les programmeurs ont généralement besoin. De cette façon, le langage de programmation est immédiatement utile à un plus large éventail de programmeurs. Les bibliothèques peuvent être mises à jour et étendues sans modifier les fonctionnalités principales du langage lui-même.

69
Vincent Ramdhanie

En plus de ce que les autres réponses ont déjà dit, mettre des fonctions standard dans une bibliothèque c'est la séparation des préoccupations:

  • C'est le travail du compilateur d'analyser le langage et de générer du code pour celui-ci. Ce n'est pas le travail du compilateur de contenir tout ce qui peut déjà être écrit dans cette langue et fourni sous forme de bibliothèque.

  • C'est le travail de la bibliothèque standard (celui qui est toujours implicitement disponible) pour fournir la fonctionnalité core qui est nécessaire à pratiquement tous les programmes. Ce n'est pas le travail de la bibliothèque standard de contenir toutes les fonctions qui pourraient être utiles.

  • C'est le travail des bibliothèques standard en option de fournir des fonctionnalités auxiliaires dont de nombreux programmes peuvent se passer, mais qui sont encore assez basiques et également essentielles pour de nombreuses applications pour garantir la livraison dans des environnements standard. Ce n'est pas le travail de ces bibliothèques optionnelles de contenir tout le code réutilisable qui a jamais été écrit.

  • C'est le travail des bibliothèques utilisateur de fournir des collections de fonctions réutilisables utiles. Ce n'est pas le travail des bibliothèques utilisateur de contenir tout le code jamais écrit.

  • C'est le travail du code source d'une application de fournir les bits de code restants qui ne sont vraiment pertinents que pour cette application.

Si vous voulez un logiciel unique, vous obtenez quelque chose de incroyablement complexe. Vous devez modulariser pour réduire la complexité à des niveaux gérables. Et vous devez modulariser pour permettre des implémentations partielles :

  • La bibliothèque de threading ne vaut rien sur le contrôleur intégré à cœur unique. Permettre à l'implémentation du langage pour ce contrôleur intégré de ne pas inclure la bibliothèque pthread est juste la bonne chose à faire.

  • La bibliothèque mathématique ne vaut rien sur le micro-contrôleur qui n'a même pas de FPU. Encore une fois, ne pas être obligé de fournir des fonctions comme sin() rend la vie beaucoup plus facile pour les implémenteurs de votre langage pour ce microcontrôleur.

  • Même la bibliothèque standard de base n'a aucune valeur lorsque vous programmez un noyau. Vous ne pouvez pas implémenter write() sans appel système dans le noyau, et vous ne pouvez pas implémenter printf() sans write(). En tant que programmeur du noyau, c'est votre travail de fournir le syscall write(), vous ne pouvez pas vous attendre à ce qu'il soit là.

Un langage qui ne permet pas de telles omissions dans les bibliothèques standard n'est tout simplement pas adapté à de nombreuses tâches . Si vous souhaitez que votre langue soit utilisable de manière flexible dans des environnements inhabituels, elle doit être flexible dans les bibliothèques standard incluses. Plus votre langage s'appuie sur des bibliothèques standard, plus il fait d'hypothèses sur son environnement d'exécution, et restreint ainsi son utilisation aux environnements qui fournissent ces prérequis.

Bien sûr, des langages de haut niveau comme python et Java peut faire un lot d'hypothèses sur leur environnement. Et ils ont tendance à inclure beaucoup, beaucoup de choses dans leurs bibliothèques standard. Les langages de niveau inférieur comme C fournissent beaucoup moins dans leurs bibliothèques standard, et gardent la bibliothèque standard de base beaucoup plus petite. C'est pourquoi vous trouvez un compilateur C fonctionnel pour pratiquement n'importe quelle architecture, mais peut ne pas être en mesure d'exécuter de scripts python dessus).

Une raison majeure pour laquelle les compilateurs et les bibliothèques standard sont séparés est parce qu'ils servent deux objectifs différents (même s'ils sont tous deux définis par la même spécification de langage): le compilateur traduit le code de niveau supérieur en instructions machine, et la bibliothèque standard fournit des tests pré-testés implémentations de fonctionnalités couramment nécessaires. Les rédacteurs du compilateur apprécient la modularité comme les autres développeurs de logiciels. En fait, certains des premiers compilateurs C ont encore divisé le compilateur en programmes séparés pour le prétraitement, la compilation et la liaison.

Cette modularité vous offre de nombreux avantages:

  • Il minimise la quantité de travail nécessaire lors de la prise en charge d'une nouvelle plate-forme matérielle, car la plupart du code de bibliothèque standard est indépendant du matériel peut être réutilisé.
  • Une implémentation de bibliothèque standard peut être optimisée de différentes manières (pour la vitesse, l'espace, l'utilisation des ressources, etc.). De nombreux systèmes informatiques antérieurs ne disposaient que d'un seul compilateur, et le fait d'avoir une bibliothèque standard séparée signifiait que les développeurs pouvaient échanger les implémentations en fonction de leurs besoins.
  • La fonctionnalité de bibliothèque standard n'a même pas besoin d'exister. Lorsque vous écrivez du code C nu par exemple, vous avez un compilateur complet, mais la plupart des fonctionnalités de bibliothèque standard ne sont pas là et certaines choses comme les E/S de fichiers ne sont même pas possibles. Si le compilateur était requis pour implémenter cette fonctionnalité, vous ne pouviez pas disposer d'un compilateur C conforme aux normes sur certaines des plates-formes où vous en avez le plus besoin.
  • Sur les premiers systèmes, les compilateurs étaient fréquemment développés par la société qui a conçu le matériel. Les bibliothèques standard étaient fréquemment fournies par le fournisseur du système d'exploitation, car elles nécessitaient souvent l'accès à des fonctionnalités (comme les appels système) spécifiques à cette plate-forme logicielle. Il n'était pas pratique pour un rédacteur de compilateur de devoir prendre en charge toutes les différentes combinaisons de matériel et de logiciel (il y avait auparavant beaucoup plus de variété à la fois dans l'architecture matérielle et la plate-forme logicielle).
  • Dans les langages de haut niveau, une bibliothèque standard peut être implémentée en tant que bibliothèque chargée dynamiquement. Une implémentation de bibliothèque standard peut ensuite être utilisée par plusieurs compilateurs et/ou langages de programmation.

Historiquement parlant (du moins du point de vue de C), les versions originales de pré-standardisation du langage n'avaient pas du tout de bibliothèque standard. Les fournisseurs de systèmes d'exploitation et les tiers fournissaient souvent des bibliothèques pleines de fonctionnalités couramment utilisées, mais les différentes implémentations incluaient des choses différentes et elles étaient largement incompatibles entre elles. Lorsque C a été normalisé, ils ont défini une "bibliothèque standard" afin d'harmoniser ces implémentations disparates et d'améliorer la portabilité. La bibliothèque standard C développée séparément du langage, comme les bibliothèques Boost pour C++, mais ont ensuite été intégrées dans la spécification du langage.

15
bta

Réponse supplémentaire au cas d'angle: gestion de la propriété intellectuelle

Un exemple notable est implémentation de Math.Pow (double, double) dans .NET Framework qui a été acheté par Microsoft auprès d'Intel et reste non divulgué même si le cadre est devenu open-source. (Pour être précis, dans le cas ci-dessus, il s'agit d'un appel interne plutôt que d'une bibliothèque, mais l'idée tient.) Une bibliothèque séparée de la langue elle-même (théoriquement aussi un sous-ensemble de bibliothèques standard) peut donner aux bailleurs de langue plus de flexibilité pour dessiner le ligne entre ce qui doit rester transparent et ce qui doit rester non divulgué (en raison de leurs contrats avec des tiers ou pour d'autres raisons liées à la propriété intellectuelle).

5
miroxlav

En tant que concepteur de langues moi-même, je voudrais faire écho à certaines des autres réponses ici, mais les fournir à travers les yeux de quelqu'un qui construit une langue.

Une API n'est pas terminée lorsque vous avez terminé d'y ajouter tout ce que vous pouvez. Une API est terminée lorsque vous avez fini d'en retirer tout ce que vous pouvez.

Un langage de programmation doit être spécifié en utilisant un certain langage. Vous devez être en mesure de transmettre le sens de tout programme écrit dans votre langue. Cette langue est très difficile à écrire, et encore plus difficile à bien écrire. En général, il s'agit d'une forme d'anglais très précise et bien structurée utilisée pour transmettre du sens non pas à l'ordinateur, mais à d'autres développeurs, en particulier ceux qui écrivent des compilateurs ou des interprètes pour votre langue. Voici un exemple de la spécification C++ 11, [intro.multithread/14]:

La séquence visible d'effets secondaires sur un objet atomique M, par rapport à un calcul de valeur B de M, est une sous-séquence contiguë maximale d'effets secondaires dans l'ordre de modification de M, où le premier effet secondaire est visible par rapport à B , et pour chaque effet secondaire, ce n'est pas le cas que B arrive avant lui. La valeur d'un objet atomique M, telle que déterminée par l'évaluation B, doit être la valeur stockée par une opération dans la séquence visible de M par rapport à B. [Remarque: On peut montrer que la séquence visible d'effets secondaires d'une valeur le calcul est unique compte tenu des exigences de cohérence ci-dessous. —Fin note]

Blek! Quiconque a franchi le pas pour comprendre comment le C++ 11 gère le multithreading peut comprendre pourquoi le libellé doit être si opaque, mais cela ne pardonne pas le fait qu'il est ... eh bien ... si opaque!

Comparez cela avec la définition de std::shared_ptr<T>::reset, Dans la section bibliothèque de la norme:

template <class Y> void reset(Y* p);

Effets: Équivalent à shared_ptr(p).swap(*this)

Alors quelle est la différence? Dans la partie définition du langage, les auteurs ne peuvent pas supposer que le lecteur comprend les primitives du langage. Tout doit être précisé soigneusement en prose anglaise. Une fois que nous arrivons à la partie de définition de bibliothèque, nous pouvons utiliser le langage pour spécifier le comportement. C'est souvent beaucoup plus facile!

En principe, on pourrait avoir une accumulation en douceur à partir des primitives au début du document de spécification, tout au long de la définition de ce que nous pourrions considérer comme des "fonctionnalités de bibliothèque standard", sans avoir à tracer une ligne entre les "primitives de langage" et fonctionnalités "bibliothèque standard". En pratique, cette ligne s'avère extrêmement utile à tracer car elle vous permet d'écrire certaines des parties les plus complexes du langage (telles que celles qui doivent implémenter des algorithmes) en utilisant un langage conçu pour les exprimer.

Et nous voyons en effet des lignes floues:

  • En Java, Java.lang.ref.Reference<T> Peut niquement être sous-classé par les classes de bibliothèque standard Java.lang.ref.WeakReference<T>Java.lang.ref.SoftReference<T> Et Java.lang.ref.PhantomReference<T> Car les comportements de Reference sont si profondément entrelacés avec la spécification de langage Java Java qu'ils ont dû mettre des restrictions dans la partie de ce processus implémentée en tant que classes de "bibliothèque standard".
  • En C #, il existe une classe, System.Delegate qui encapsule le concept de délégués. Malgré son nom, ce n'est pas un délégué. Il s'agit également d'une classe abstraite (ne peut pas être instanciée) à partir de laquelle vous ne pouvez pas créer de classes dérivées. Seul le système peut le faire grâce à des fonctionnalités écrites dans la spécification du langage.
5
Cort Ammon

Bugs et débogage.

Bogues: Tous les logiciels ont des bogues, votre bibliothèque standard a des bogues et votre compilateur a des bogues. En tant qu'utilisateur de la langue, il est beaucoup plus facile de trouver et de contourner ces bogues lorsqu'ils se trouvent dans la bibliothèque standard plutôt que dans le compilateur.

Débogage: il est beaucoup plus facile pour moi de voir une trace de pile d'une bibliothèque standard et de me donner une idée de ce qui pourrait mal se passer. Parce que cette trace de pile contient du code que je comprends. Bien sûr, vous pouvez creuser plus profondément et vous pouvez également retracer vos fonctions intrinsèques, mais c'est beaucoup plus facile si c'est dans une langue que vous utilisez tout le temps de jour en jour.

4
Pieter B

Ceci est une excellente question!

L'état de l'art

Le standard C++, par exemple, ne spécifie jamais ce qui doit être implémenté dans le compilateur ou dans la bibliothèque standard: il fait simplement référence à l'implémentation . Par exemple, les symboles réservés sont définis à la fois par le compilateur (comme intrinsèques) et par la bibliothèque standard, de manière interchangeable.

Pourtant, toutes les implémentations C++ que je connais auront le nombre minimum possible d'intrinsèques fourni par le compilateur, et autant que possible fourni par la bibliothèque standard.

Ainsi, bien qu'il soit techniquement possible de définir la bibliothèque standard en tant que fonctionnalité intrinsèque dans le compilateur, elle semble rarement utilisée dans la pratique.

Pourquoi?

Examinons l'idée de déplacer un élément de fonctionnalité de la bibliothèque standard vers le compilateur.

Avantages:

  • De meilleurs diagnostics: les intrinsèques peuvent être dans un boîtier spécial.
  • Meilleures performances: les intrinsèques peuvent être dans un boîtier spécial.

Inconvénients:

  • Augmentation de la masse du compilateur: chaque cas spécial ajoute de la complexité au compilateur; la complexité augmente les coûts de maintenance et la probabilité de bugs.
  • Itération plus lente: changer l'implémentation de la fonctionnalité nécessite de changer le compilateur lui-même, ce qui rend plus difficile la création d'une petite bibliothèque (en dehors de std) à expérimenter.
  • Barre d'entrée plus élevée: plus il est cher/difficile de changer quelque chose, moins il y a de chances que les gens s'y joignent.

Cela signifie que déplacer quelque chose vers le compilateur coûte cher , maintenant et dans le futur, et qu'il nécessite donc un dossier solide. Pour certaines fonctionnalités, il est nécessaire (elles ne peuvent pas être écrites en code normal), mais même alors, il est avantageux d'extraire des pièces minimales et génériques à déplacer au compilateur et construisez au-dessus d'eux dans la bibliothèque standard.

4
Matthieu M.

Ceci est censé être un ajout aux réponses existantes (et est trop long pour un commentaire).

Il existe au moins deux autres raisons pour une bibliothèque standard:

Barrière à l'entrée

Si une fonction de langue particulière est dans une fonction de bibliothèque et que je veux savoir comment cela fonctionne, je peux simplement lire la source de cette fonction. Si je veux soumettre un rapport de bogue/patch/demande d'extraction, il n'est généralement pas trop difficile de coder un correctif et des cas de test. Si c'est dans le compilateur, je dois pouvoir creuser dans les internes. Même s'il est dans le même langage (et il devrait l'être, tout compilateur qui se respecte devrait être auto-hébergé), le code du compilateur ne ressemble en rien au code d'application. Cela peut prendre une éternité pour même trouver les bons fichiers.

Vous vous coupez de beaucoup de contributeurs potentiels si vous suivez cette voie.

Chargement de code à chaud

De nombreuses langues offrent cette fonctionnalité à un degré ou à un autre, mais il serait extrêmement compliqué de recharger à chaud le code qui effectue le rechargement à chaud. Si le SL est distinct du runtime, il peut être rechargé.

1
Jared Smith

C'est une question intéressante mais il y a déjà beaucoup de bonnes réponses, donc je ne tenterai pas une réponse complète.

Cependant, deux choses qui, selon moi, n'ont pas suffisamment retenu l'attention:

Premièrement, le tout n'est pas très clair. C'est un peu un spectre exactement parce qu'il y a des raisons de faire les choses différemment. Par exemple, les compilateurs connaissent souvent les bibliothèques standard et leurs fonctions. Exemple de l'exemple: la fonction "Hello World" de C - printf - est la meilleure à laquelle je puisse penser. C'est une fonction de bibliothèque, elle doit l'être, car elle dépend beaucoup de la plate-forme. Mais son comportement (défini par l'implémentation) doit être connu du compilateur afin d'avertir le programmeur des mauvaises invocations. Ce n'est pas particulièrement soigné, mais a été considéré comme un bon compromis. Soit dit en passant, c'est la vraie réponse à la plupart des questions "pourquoi cette conception": beaucoup de compromis et "semblait être une bonne idée à l'époque". Pas toujours le "c'était la manière claire de le faire" ou "le regard: un exemple de l'une des extrémités du spectre" comme souvent donné par les partisans de ce choix. (Non pas que ces réponses ne soient pas pertinentes).

Deuxièmement, cela permet à la bibliothèque standard de ne pas être toute cette norme. Il y a beaucoup de situations où une langue est souhaitable, mais les bibliothèques standard qui les accompagnent généralement ne sont pas à la fois pratiques et souhaitables. C'est le plus souvent le cas avec des langages de programmation de systèmes comme C, sur des plateformes non standard. Par exemple, si vous avez un système sans OS ni planificateur: vous n'aurez pas de thread.

Avec un modèle de bibliothèque standard (et le threading étant pris en charge), cela peut être géré proprement: le compilateur est à peu près le même, vous pouvez réutiliser les bits des bibliothèques qui s'appliquent, et tout ce que vous ne pouvez pas supprimer. Si cela est intégré au compilateur, les choses commencent à devenir désordonnées.

Par exemple:

  • Vous ne pouvez pas être un compilateur conforme.

  • Comment indiqueriez-vous votre écart par rapport à la norme. Notez qu'il y a généralement une forme de syntaxe d'importation/inclusion que vous pouvez échouer, c'est-à-dire l'importation de pythons ou l'inclusion de C qui pointe facilement vers le problème s'il manque quelque chose dans le modèle de bibliothèque standard.

Des problèmes similaires s'appliquent également si vous souhaitez modifier ou étendre la fonctionnalité de "bibliothèque". C'est beaucoup plus courant que vous ne le pensez. Juste pour rester avec le threading: Windows, Linux et certaines unités de traitement de réseau exotiques font toutes le threading de manière très différente. Alors que les bits linux/windows peuvent être assez statiques et pouvoir utiliser une API identique, le contenu NPU changera avec le jour de la semaine et l'API avec lui. Les compilateurs dévieraient rapidement au fur et à mesure que les gens décidaient quels bits ils devaient prendre en charge/pourraient faire sans assez rapidement s'il n'y avait aucun moyen de séparer ce genre de chose.

1
drjpizzle