it-swarm.com.de

Warum programmieren Standardbibliotheken keine Grundelemente für Programmiersprachen?

Ich dachte, warum gibt es (in allen Programmiersprachen, die ich gelernt habe, wie C++, Java, Python) Standardbibliotheken wie stdlib, anstatt ähnliche "Funktionen" zu haben, die ein Grundelement der Sprache selbst sind.

32
user327143

Lassen Sie mich etwas näher erläutern @ Vincents (+1) gute Antwort :

Warum konnte der Compiler einen Funktionsaufruf nicht einfach in eine Reihe von Anweisungen übersetzen?

Dies kann und geschieht über mindestens zwei Mechanismen:

  • Inlining eines Funktionsaufrufs - Während der Übersetzung kann der Compiler einen Quellcode-Aufruf durch seine Implementierung direkt inline ersetzen, anstatt die Funktion tatsächlich aufzurufen. Für die Funktion muss jedoch irgendwo eine Implementierung definiert sein, die sich in der Standardbibliothek befinden kann.

  • intrinsische Funktion - intrinsische Funktionen sind Funktionen, über die der Compiler informiert wurde, ohne die Funktion unbedingt in einer Bibliothek zu finden. Diese sind normalerweise für Hardwarefunktionen reserviert, auf die auf andere Weise praktisch nicht zugegriffen werden kann, da sie so einfach sind, dass selbst der Aufwand für den Aufruf der Funktion der Assembler-Bibliothek als hoch angesehen wird. (Der Compiler kann im Allgemeinen nur automatisch Quellcode in seiner Sprache einbinden, nicht jedoch Assembly-Funktionen, bei denen der eigentliche Mechanismus zum Tragen kommt.)

Trotzdem ist es manchmal die beste Option für den Compiler, einen Funktionsaufruf in der Ausgangssprache in einen Funktionsaufruf im Maschinencode zu übersetzen. Rekursion, virtuelle Methoden und Größe sind einige Gründe, warum Inlining nicht immer möglich/praktisch ist. (Ein weiterer Grund ist die Absicht des Builds, z. B. separate Kompilierung (Objektmodule), separate Ladeeinheiten (z. B. DLLs)).

Es ist auch kein wirklicher Vorteil, die meisten Standardbibliotheksfunktionen kompliziert zu gestalten (das würde viel mehr Wissen in den Compiler codieren, ohne einen wirklichen Vorteil zu haben), daher ist ein erneuter Aufruf des Maschinencodes häufig am besten geeignet.

C ist eine bemerkenswerte Sprache, in der möglicherweise andere explizite Sprachanweisungen zugunsten von Standardbibliotheksfunktionen weggelassen wurden. Obwohl es bereits Bibliotheken gab, verlagerte sich diese Sprache dahin, mehr Arbeit von Standardbibliotheksfunktionen zu erledigen und weniger als explizite Aussagen in der Grammatik der Sprache. IO in anderen Sprachen erhielt beispielsweise häufig eine eigene Syntax in Form verschiedener Anweisungen, während die C-Grammatik keine IO -Anweisungen, stattdessen einfach auf die Standardbibliothek zurückgreifen, um diese bereitzustellen, auf die alle über Funktionsaufrufe zugegriffen werden kann, die der Compiler bereits zu tun weiß.

31
Erik Eidt

Dies dient einfach dazu, die Sprache selbst so einfach wie möglich zu halten. Sie müssen zwischen einer Funktion der Sprache unterscheiden, z. B. einer Art Schleife oder Möglichkeiten zum Übergeben von Parametern an Funktionen usw., und allgemeinen Funktionen, die die meisten Anwendungen benötigen.

Bibliotheken sind Funktionen, die für viele Programmierer nützlich sein können, sodass sie als wiederverwendbarer Code erstellt werden, der gemeinsam genutzt werden kann. Die Standardbibliotheken sind so konzipiert, dass sie sehr häufige Funktionen sind, die Programmierer normalerweise benötigen. Auf diese Weise ist die Programmiersprache für ein breiteres Spektrum von Programmierern sofort nützlich. Die Bibliotheken können aktualisiert und erweitert werden, ohne die Kernfunktionen der Sprache selbst zu ändern.

69

Zusätzlich zu dem, was die anderen Antworten bereits gesagt haben, ist das Einfügen von Standardfunktionen in eine Bibliothek Trennung von Bedenken:

  • Es ist die Aufgabe des Compilers, die Sprache zu analysieren und Code dafür zu generieren. Es ist nicht die Aufgabe des Compilers, etwas zu enthalten, das bereits in dieser Sprache geschrieben und als Bibliothek bereitgestellt werden kann.

  • Es ist die Aufgabe der Standardbibliothek (die immer implizit verfügbar ist), Kern Funktionalität bereitzustellen, die von praktisch allen Programmen benötigt wird. Es ist nicht die Aufgabe der Standardbibliothek, alle Funktionen zu enthalten, die nützlich sein könnten.

  • Es ist die Aufgabe optionaler Standardbibliotheken, zusätzliche Funktionen bereitzustellen, auf die viele Programme verzichten können, die jedoch noch recht einfach sind und für viele Anwendungen auch unerlässlich sind, um den Versand in Standardumgebungen zu gewährleisten. Es ist nicht die Aufgabe dieser optionalen Bibliotheken, den gesamten wiederverwendbaren Code zu enthalten, der jemals geschrieben wurde.

  • Es ist die Aufgabe der Benutzerbibliotheken, Sammlungen nützlicher wiederverwendbarer Funktionen bereitzustellen. Es ist nicht die Aufgabe von Benutzerbibliotheken, den gesamten Code zu enthalten, der jemals geschrieben wurde.

  • Es ist die Aufgabe des Quellcodes einer Anwendung, die verbleibenden Codebits bereitzustellen, die wirklich nur für diese eine Anwendung relevant sind.

Wenn Sie eine einheitliche Software wünschen, erhalten Sie etwas wahnsinnig Komplexes. Sie müssen modularisieren, um die Komplexität auf ein überschaubares Maß zu reduzieren. Und Sie müssen modularisieren, um teilweise Implementierungen zu ermöglichen:

  • Die Threading-Bibliothek ist auf dem Single-Core-Embedded-Controller wertlos. Es ist genau das Richtige, wenn die Sprachimplementierung für diesen eingebetteten Controller die Bibliothek pthread nicht enthält.

  • Die Mathematikbibliothek ist auf dem Mikrocontroller ohne FPU wertlos. Auch hier macht es den Implementierern Ihrer Sprache für diesen Mikrocontroller das Leben viel einfacher, nicht gezwungen zu sein, Funktionen wie sin() bereitzustellen.

  • Selbst die Kernstandardbibliothek ist wertlos, wenn Sie einen Kernel programmieren. Sie können write() nicht ohne einen Systemaufruf in den Kernel implementieren, und Sie können printf() nicht ohne write() implementieren. Als Kernel-Programmierer ist es Ihre Aufgabe, den Systemaufruf write() bereitzustellen. Sie können nicht einfach erwarten, dass er vorhanden ist.

Eine Sprache, die solche Auslassungen in den Standardbibliotheken nicht zulässt , ist einfach nicht für viele Aufgaben geeignet . Wenn Sie möchten, dass Ihre Sprache in ungewöhnlichen Umgebungen flexibel verwendet werden kann, muss sie in den enthaltenen Standardbibliotheken flexibel sein. Je mehr Ihre Sprache auf Standardbibliotheken angewiesen ist, desto mehr Annahmen werden in ihrer Ausführungsumgebung getroffen, und daher wird ihre Verwendung auf Umgebungen beschränkt, die diese Voraussetzungen bieten.

Natürlich können Hochsprachen wie python und Java kann eine Menge machen von Annahmen über ihre Umgebung. Und sie neigen dazu, viele, viele Dinge in ihre Standardbibliotheken aufzunehmen. Niedrigere Sprachen wie C bieten viel weniger in ihren Standardbibliotheken und halten die Standardstandardbibliothek viel kleiner. Deshalb finden Sie einen funktionierenden C-Compiler für praktisch jede Architektur, kann jedoch möglicherweise keine python - Skripte darauf ausführen.

Ein wichtiger Grund für die Trennung von Compilern und Standardbibliotheken ist, dass sie zwei unterschiedlichen Zwecken dienen (auch wenn beide durch dieselbe Sprachspezifikation definiert sind): Der Compiler übersetzt übergeordneten Code in Maschinenanweisungen, und die Standardbibliothek bietet vorab getestete Funktionen Implementierungen häufig benötigter Funktionen. Compiler-Autoren legen Wert auf Modularität, genau wie andere Softwareentwickler. Tatsächlich teilen einige der frühen C-Compiler den Compiler weiter in separate Programme zum Vorverarbeiten, Kompilieren und Verknüpfen auf.

Diese Modularität bietet Ihnen eine Reihe von Vorteilen:

  • Es minimiert den Arbeitsaufwand für die Unterstützung einer neuen Hardwareplattform, da der größte Teil des Standardbibliothekscodes hardwareunabhängig ist und wiederverwendet werden kann.
  • Eine Standardbibliotheksimplementierung kann auf verschiedene Arten optimiert werden (hinsichtlich Geschwindigkeit, Speicherplatz, Ressourcennutzung usw.). Viele frühe Computersysteme hatten nur einen Compiler zur Verfügung, und eine separate Standardbibliothek bedeutete, dass Entwickler Implementierungen austauschen konnten, um sie ihren Anforderungen anzupassen.
  • Die Standardbibliotheksfunktionalität muss nicht einmal vorhanden sein. Wenn Sie beispielsweise Bare-Metal-C-Code schreiben, verfügen Sie über einen Compiler mit vollem Funktionsumfang, aber die meisten Standardfunktionen der Bibliothek sind nicht vorhanden, und einige Dinge wie Datei-E/A sind nicht einmal möglich. Wenn der Compiler diese Funktionalität implementieren musste, konnte auf einigen Plattformen, auf denen Sie ihn am dringendsten benötigen, kein standardkonformer C-Compiler vorhanden sein.
  • Auf frühen Systemen wurden Compiler häufig von dem Unternehmen entwickelt, das die Hardware entworfen hat. Standardbibliotheken wurden häufig vom Betriebssystemhersteller bereitgestellt, da sie häufig Zugriff auf Funktionen (wie Systemaufrufe) erforderten, die für diese Softwareplattform spezifisch sind. Für einen Compiler-Writer war es unpraktisch, all die verschiedenen Kombinationen von Hardware und Software unterstützen zu müssen (früher gab es sowohl in der Hardwarearchitektur als auch in der Softwareplattform eine viel größere Vielfalt).
  • In Hochsprachen kann eine Standardbibliothek als dynamisch geladene Bibliothek implementiert werden. Eine Standardbibliotheksimplementierung kann dann von mehreren Compilern und/oder Programmiersprachen verwendet werden.

Historisch gesehen (zumindest aus Cs Sicht) hatten die ursprünglichen Vor-Standardisierungsversionen der Sprache überhaupt keine Standardbibliothek. Betriebssystemanbieter und Drittanbieter stellten häufig Bibliotheken mit häufig verwendeten Funktionen zur Verfügung. Unterschiedliche Implementierungen enthielten jedoch unterschiedliche Funktionen und waren weitgehend nicht miteinander kompatibel. Als C standardisiert wurde, definierten sie eine "Standardbibliothek", um diese unterschiedlichen Implementierungen zu harmonisieren und die Portabilität zu verbessern. Die C-Standardbibliothek wurde unabhängig von der Sprache entwickelt, wie die Boost-Bibliotheken für C++, wurde aber später in die Sprachspezifikation integriert.

15
bta

Zusätzliche Eckfallantwort: Verwaltung des geistigen Eigentums

Bemerkenswertes Beispiel ist Implementierung von Math.Pow (double, double) in .NET Framework , das von Microsoft von Intel gekauft wurde und auch dann nicht bekannt gegeben wird, wenn das Framework Open Source wurde. (Um genau zu sein, handelt es sich im obigen Fall eher um einen internen Aufruf als um eine Bibliothek, aber die Idee gilt.) Eine von der Sprache selbst getrennte Bibliothek (theoretisch auch eine Teilmenge von Standardbibliotheken) kann den Sprachunterstützern mehr Flexibilität beim Zeichnen der Bibliothek geben Grenze zwischen dem, was transparent zu halten ist und dem, was nicht bekannt gegeben werden muss (aufgrund von Verträgen mit Dritten oder aus anderen Gründen im Zusammenhang mit geistigem Eigentum).

5
miroxlav

Als Sprachdesigner möchte ich einige der anderen Antworten hier wiederholen, sie aber mit den Augen von jemandem bereitstellen, der eine Sprache baut.

Eine API ist noch nicht fertig, wenn Sie alles hinzugefügt haben, was Sie können. Eine API ist fertig, wenn Sie alles getan haben, was Sie können.

Eine Programmiersprache muss in einer bestimmten Sprache angegeben werden. Sie müssen in der Lage sein, die Bedeutung jedes in Ihrer Sprache geschriebenen Programms zu vermitteln. Diese Sprache ist sehr schwer zu schreiben und noch schwerer gut zu schreiben. Im Allgemeinen handelt es sich in der Regel um eine sehr präzise und gut strukturierte Form des Englischen, die verwendet wird, um nicht dem Computer, sondern anderen Entwicklern, insbesondere den Entwicklern, die Compiler oder Dolmetscher für Ihre Sprache schreiben, Bedeutung zu vermitteln. Hier ist ein Beispiel aus der C++ 11-Spezifikation [intro.multithread/14]:

Die sichtbare Folge von Nebenwirkungen auf ein Atomobjekt M in Bezug auf eine Wertberechnung B von M ist eine maximal zusammenhängende Teilfolge von Nebenwirkungen in der Modifikationsreihenfolge von M, wobei die erste Nebenwirkung in Bezug auf B sichtbar ist und für jede Nebenwirkung ist es nicht so, dass B davor auftritt. Der Wert eines Atomobjekts M, wie er durch Auswertung B bestimmt wird, muss der Wert sein, der durch eine Operation in der sichtbaren Folge von M in Bezug auf B gespeichert wird. [Anmerkung: Es kann gezeigt werden, dass die sichtbare Folge von Nebenwirkungen eines Wertes Die Berechnung ist angesichts der folgenden Kohärenzanforderungen eindeutig. - Endnote]

Blek! Jeder, der den Sprung gewagt hat, zu verstehen, wie C++ 11 mit Multithreading umgeht, kann verstehen, warum der Wortlaut so undurchsichtig sein muss, aber das verzeiht nicht die Tatsache, dass er ... nun ... so undurchsichtig ist!

Vergleichen Sie dies mit der Definition von std::shared_ptr<T>::reset Im Bibliotheksabschnitt des Standards:

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

Effekte : Entspricht shared_ptr(p).swap(*this)

Was ist der Unterschied? Im Teil zur Sprachdefinition können die Autoren nicht davon ausgehen, dass der Leser die Sprachprimitive versteht. Alles muss sorgfältig in englischer Prosa angegeben werden. Sobald wir den Bibliotheksdefinitionsteil erreicht haben, können wir die Sprache verwenden, um das Verhalten anzugeben. Das ist oft viel einfacher!

Im Prinzip könnte man zu Beginn des Spezifikationsdokuments einen reibungslosen Aufbau von Grundelementen haben, bis man definiert, was wir als "Standardbibliotheksfunktionen" betrachten würden, ohne eine Grenze zwischen "Sprachprimitiven" und "Grundelementen" ziehen zu müssen Funktionen der "Standardbibliothek". In der Praxis erweist sich diese Linie als enorm wertvoll zum Zeichnen, da Sie damit einige der komplexesten Teile der Sprache (z. B. diejenigen, die Algorithmen implementieren müssen) in einer Sprache schreiben können, die sie ausdrücken soll.

Und wir sehen tatsächlich einige verschwommene Linien:

  • In Java kann Java.lang.ref.Reference<T>nur von den Standardbibliotheksklassen Java.lang.ref.WeakReference<T>Java.lang.ref.SoftReference<T> Und Java.lang.ref.PhantomReference<T> Untergeordnet werden, da das Verhalten von Reference sind so tief mit der Sprachspezifikation Java) verwoben, dass sie einige Einschränkungen für den Teil dieses Prozesses vornehmen mussten, der als "Standardbibliotheks" -Klassen implementiert ist.
  • In C # gibt es eine Klasse, System.Delegate, die das Konzept der Delegaten kapselt. Trotz seines Namens ist es kein Delegierter. Es ist auch eine abstrakte Klasse (kann nicht instanziiert werden), aus der Sie keine abgeleiteten Klassen erstellen können. Nur das System kann dies durch Funktionen tun, die in die Sprachspezifikation geschrieben sind.
5
Cort Ammon

Bugs und Debugging.

Fehler: Alle Software hat Fehler, Ihre Standardbibliothek hat Fehler und Ihr Compiler hat Fehler. Als Benutzer der Sprache ist es viel einfacher, solche Fehler zu finden und zu umgehen, wenn sie sich in der Standardbibliothek befinden, als im Compiler.

Debuggen: Es ist für mich viel einfacher, eine Stapelverfolgung einer Standardbibliothek zu sehen und mir ein Gefühl dafür zu geben, was möglicherweise schief geht. Weil dieser Stack-Trace Code hat, verstehe ich. Natürlich können Sie tiefer graben und auch Ihre intrinsischen Funktionen verfolgen, aber es ist viel einfacher, wenn es in einer Sprache ist, die Sie von Tag zu Tag verwenden.

4
Pieter B

Das ist eine ausgezeichnete Frage!

Der letzte Stand der Technik

Der C++ - Standard gibt beispielsweise niemals an, was im Compiler oder in der Standardbibliothek implementiert werden soll: Er bezieht sich nur auf die Implementierung . Beispielsweise werden reservierte Symbole sowohl vom Compiler (als Intrinsics) als auch von der Standardbibliothek austauschbar definiert.

Alle mir bekannten C++ - Implementierungen haben jedoch die minimal mögliche Anzahl von Intrinsics, die vom Compiler und so viel wie möglich von der Standardbibliothek bereitgestellt werden.

Während es technisch machbar ist, die Standardbibliothek als intrinsische Funktionalität im Compiler zu definieren, scheint sie in der Praxis selten verwendet zu werden.

Warum?

Betrachten wir die Idee, einige Funktionen aus der Standardbibliothek in den Compiler zu verschieben.

Vorteile :

  • Bessere Diagnose: Intrinsics können speziell behandelt werden.
  • Bessere Leistung: Intrinsics können speziell behandelt werden.

Nachteile :

  • Erhöhte Compilermasse: Jeder Sonderfall erhöht die Komplexität des Compilers. Komplexität erhöht die Wartungskosten und die Wahrscheinlichkeit von Fehlern.
  • Langsamere Iteration: Um die Implementierung der Funktionalität zu ändern, muss der Compiler selbst geändert werden, was es schwieriger macht, nur eine kleine Bibliothek (außerhalb von std) zum Experimentieren zu erstellen.
  • Höhere Eintrittsbarriere: Je teurer/schwieriger es ist, etwas zu ändern, desto weniger Menschen springen wahrscheinlich ein.

Dies bedeutet, dass das Verschieben von etwas in den Compiler jetzt und in Zukunft teuer ist und daher einen soliden Fall erfordert. Für einige Funktionen ist dies erforderlich (sie können nicht als regulärer Code geschrieben werden), aber selbst dann lohnt es sich, minimale und generische Teile zu extrahieren, um sie zu verschieben an den Compiler und bauen Sie darauf in der Standardbibliothek.

4
Matthieu M.

Dies ist als Ergänzung zu den vorhandenen Antworten gedacht (und für einen Kommentar zu lang).

Es gibt mindestens zwei weitere Gründe für eine Standardbibliothek:

Zutrittsschranke

Wenn sich eine bestimmte Sprachfunktion in einer Bibliotheksfunktion befindet und ich wissen möchte, wie sie funktioniert, kann ich einfach die Quelle für diese Funktion lesen. Wenn ich eine Fehlerbericht-/Patch-/Pull-Anfrage senden möchte, ist es im Allgemeinen nicht allzu schwierig, einen Fix- und Testfall zu codieren. Wenn es im Compiler ist, muss ich in der Lage sein, in die Interna zu graben. Selbst wenn es in derselben Sprache ist (und es sollte so sein, dass jeder Compiler mit Selbstachtung selbst gehostet werden sollte), ist Compilercode nichts anderes als Anwendungscode. Es kann ewig dauern, bis die richtigen Dateien gefunden sind.

Sie schneiden sich von vielen potenziellen Mitwirkenden ab, wenn Sie diesen Weg gehen.

Hot Code wird geladen

Viele Sprachen bieten diese Funktion bis zu dem einen oder anderen Grad an, aber es wäre enorm kompliziert, den Code, der das Hot-Reload durchführt, im laufenden Betrieb neu zu laden. Wenn der SL von der Laufzeit getrennt ist, kann er neu geladen werden.

1
Jared Smith

Dies ist eine interessante Frage, aber es gibt bereits viele gute Antworten, daher werde ich keine vollständige versuchen.

Zwei Dinge, von denen ich glaube, dass sie nicht genug Beachtung gefunden haben:

Erstens ist das Ganze nicht sehr eindeutig. Es ist ein bisschen wie ein Spektrum, gerade weil es Gründe gibt, Dinge anders zu machen. Beispielsweise kennen Compiler häufig Standardbibliotheken und ihre Funktionen. Beispiel für das Beispiel: Cs "Hello World" -Funktion - printf - ist die beste, die ich mir vorstellen kann. Es ist eine Bibliotheksfunktion, es muss eine Art sein, da es sehr plattformabhängig ist. Das Verhalten (Implementierung definiert) muss dem Compiler jedoch bekannt sein, um den Programmierer vor fehlerhaften Aufrufen zu warnen. Dies ist nicht besonders ordentlich, wurde aber als guter Kompromiss angesehen. Dies ist übrigens die eigentliche Antwort auf die meisten Fragen zum Thema "Warum dieses Design?": Viel Kompromiss und "schien damals eine gute Idee zu sein". Nicht immer das "das war der klare Weg, es zu tun" oder "schauen: ein Beispiel von einem der anderen Enden des Spektrums", wie es oft von den Befürwortern dieser Wahl gegeben wird. (Nicht dass diese Antworten nicht relevant wären).

Zweitens kann die Standardbibliothek nicht der gesamte Standard sein. Es gibt viele Situationen, in denen eine Sprache wünschenswert ist, aber die Standardbibliotheken, die sie normalerweise begleiten, sind nicht sowohl praktisch als auch wünschenswert. Dies ist am häufigsten bei Systemprogrammiersprachen wie C auf nicht standardmäßigen Plattformen der Fall. Wenn Sie beispielsweise ein System ohne Betriebssystem oder Scheduler haben, wird kein Threading ausgeführt.

Mit einem Standardbibliotheksmodell (und dem darin unterstützten Threading) kann dies sauber gehandhabt werden: Der Compiler ist ziemlich identisch, Sie können die Bits der zutreffenden Bibliotheken wiederverwenden und alles, was Sie nicht entfernen können. Wenn dies in den Compiler eingebrannt wird, werden die Dinge unordentlich.

Zum Beispiel:

  • Sie können kein kompatibler Compiler sein.

  • Wie würden Sie Ihre Abweichung vom Standard angeben? Beachten Sie, dass es normalerweise eine Form der Import-/Include-Syntax gibt, bei der ein Fehler auftreten kann, d. H. Der Import von Pythons oder C-Includes, die leicht auf das Problem hinweisen, wenn im Standardbibliotheksmodell etwas fehlt.

Ähnliche Probleme treten auch auf, wenn Sie die Bibliotheksfunktionalität optimieren oder erweitern möchten. Dies ist weitaus häufiger als Sie vielleicht denken. Nur um beim Threading zu bleiben: Windows, Linux und einige exotische Netzwerkverarbeitungseinheiten machen das Threading ganz anders. Während die Linux/Windows-Bits möglicherweise ziemlich statisch sind und eine identische API verwenden können, ändert sich das NPU-Material mit dem Wochentag und die API damit. Compiler würden schnell abweichen, da die Leute schnell entschieden, welche Teile sie unterstützen/ohne sie auskommen könnten, wenn es keine Möglichkeit gäbe, solche Dinge aufzuteilen.

1
drjpizzle