Dennoch ist C eine einfache Sprache


In den letzten zehn Jahren seit dem Aufkommen der C-Sprache wurden viele interessante Programmiersprachen erstellt. Einige von ihnen werden immer noch verwendet, andere haben die nächste Generation von Sprachen beeinflusst, die Popularität der dritten hat leise nachgelassen. Inzwischen archaisch, kontrovers, primitiv, in den schlimmsten Traditionen seiner Generation von C-Sprachen (und seinen Erben) lebendiger als alle Lebewesen.


Kritik C ist ein klassisches Briefgenre für unsere Branche. Es klingt lauter, dann leiser, aber in letzter Zeit war es buchstäblich atemberaubend. Ein Beispiel ist eine Übersetzung von David Ciswells Artikel „C ist keine niedrige Sprache“, der vor einiger Zeit in unserem Blog veröffentlicht wurde. Man kann verschiedene Dinge über C sagen, es gibt wirklich viele unangenehme Fehler in der Gestaltung der Sprache, aber C auf der "niedrigen Ebene" abzulehnen ist zu viel!


Um eine solche Ungerechtigkeit nicht zu tolerieren, nahm ich Mut und versuchte zu entscheiden, was eine Programmiersprache auf niedriger Ebene ist und welche Praktiken sie von ihr wollten. Danach ging ich die Argumente der Kritiker C durch. So stellte sich dieser Artikel heraus.


Inhalt



Kritik Argumente C.


Hier sind einige der Argumente von Cs Kritikern, einschließlich der in einem Artikel von David Chiznell aufgeführten:


  1. Die abstrakte C-Sprachmaschine ist der veralteten PDP-11-Architektur zu ähnlich, die längst nicht mehr dem Design beliebter moderner Prozessoren entspricht.
  2. Die Nichtübereinstimmung zwischen einer abstrakten Maschine und dem Gerät realer Maschinen erschwert die Entwicklung der Optimierung von Sprachcompilern.
  3. Die Unvollständigkeit und Komplexität des Sprachstandards führt zu Diskrepanzen bei Standardimplementierungen.
  4. Die Dominanz von C-ähnlichen Sprachen erlaubt es nicht, alternative Prozessorarchitekturen zu untersuchen.

Lassen Sie uns zunächst die Anforderungen für eine einfache Sprache ermitteln und anschließend zu den angegebenen Argumenten zurückkehren.


Low-Level-Programmiersprache


Es gibt keine allgemein akzeptierte Definition einer niedrigen Sprache. Bevor jedoch kontroverse Fragen erörtert werden, ist es wünschenswert, zumindest einige anfängliche Anforderungen für das Streitgegenstand zu haben.


Niemand wird argumentieren, dass die Assemblersprache auf der niedrigsten Ebene ist. Auf jeder Plattform ist es jedoch einzigartig, sodass Code in einer solchen Sprache nicht portierbar sein kann. Selbst auf einer abwärtskompatiblen Plattform müssen Sie möglicherweise einige neue Anweisungen verwenden.


Ab hier folgt die erste Anforderung für eine einfache Sprache: Sie sollte gemeinsame Funktionen für gängige Plattformen beibehalten. Einfach ausgedrückt muss der Compiler portabel sein. Die Portabilität des Compilers vereinfacht die Entwicklung von Sprachcompilern für neue Plattformen, und die Vielzahl der von Compilern unterstützten Plattformen macht es für Entwickler überflüssig, Anwendungsprogramme für jeden neuen Computer neu zu schreiben.


Die erste Anforderung widerspricht den Wünschen der Entwickler spezieller Programme: Programmiersprachen, Treiber, Betriebssysteme und Hochleistungsdatenbanken. Die Programmierer, die diese Programme schreiben, möchten in der Lage sein, manuell zu optimieren, direkt mit dem Speicher zu arbeiten und so weiter. Kurz gesagt, eine einfache Sprache sollte es ermöglichen, mit den Details der Implementierung der Plattform zu arbeiten .


Ein Gleichgewicht zwischen diesen beiden Anforderungen zu finden - plattformübergreifende Aspekte zu identifizieren und auf so viele Details wie möglich zuzugreifen - ist ein grundlegender Grund für die Schwierigkeit, eine einfache Sprache zu entwickeln.


Beachten Sie, dass Abstraktionen auf hoher Ebene für eine solche Sprache nicht so wichtig sind - es ist wichtiger, dass sie als Vertrag zwischen der Plattform, dem Compiler und dem Entwickler dient. Und wenn es einen Vertrag gibt, ist eine Sprache erforderlich, die vom jeweiligen Implementierungsstandard unabhängig ist .


Unsere erste Anforderung - Funktionen, die Zielplattformen gemeinsam haben - wird in einer abstrakten Sprachmaschine ausgedrückt. Daher beginnen wir die Diskussion mit C.


Es geht nicht nur um PDP-11


Die Plattform, auf der die C-Sprache erschien, ist PDP-11. Es basiert auf der traditionellen von Neumann-Architektur , bei der die Programme nacheinander vom Zentralprozessor ausgeführt werden und der Speicher ein Flachband ist, auf dem sowohl die Daten als auch die Programme gespeichert sind. Eine solche Architektur lässt sich leicht in Hardware implementieren, und im Laufe der Zeit wurde sie von allen Allzweckcomputern verwendet.


Moderne Verbesserungen der von Neumann-Architektur zielen darauf ab, den Hauptengpass zu beseitigen - Verzögerungen beim Datenaustausch zwischen Prozessor und Speicher (englischer von Neuman-Engpass ). Der Unterschied in der Speicher- und CPU-Leistung führte zum Auftreten von Caching-Subsystemen von Prozessoren (einstufig und später mehrstufig).


Aber selbst Caches reichen heutzutage nicht mehr aus. Moderne Prozessoren sind superskalar geworden. Verzögerungen, wenn Befehle Daten aus dem Speicher empfangen, werden teilweise durch die außergewöhnliche Ausführung ( Parallelität auf Befehlsebene ) der Befehle in Verbindung mit dem Verzweigungsprädiktor kompensiert .


Die sequentielle abstrakte Maschine C (und viele andere Sprachen) imitiert die Arbeit nicht so sehr spezifisch von PDP-11, sondern von Computern, die nach dem Prinzip der von Neumann-Architektur angeordnet sind. Es enthält Architekturen, die auf Prozessoren mit einem einzigen Kern basieren: Desktop und Server x86, Mobile ARM, die aus der Szene von Sun / Oracle SPARC und IBM POWER stammen.


Im Laufe der Zeit wurden mehrere Prozessorkerne in einen Prozessor integriert, wodurch es notwendig wurde, die Kohärenz der Caches jedes Kerns aufrechtzuerhalten und interne Interaktionsprotokolle zu erfordern. Die Von-Neumann-Architektur wurde daher auf mehrere Kerne skaliert.


Die ursprüngliche Version der abstrakten Maschine C war sequentiell und spiegelte nicht das Vorhandensein von Programmausführungsthreads wider, die über den Speicher interagieren. Das Erscheinen des Speichermodells im Standard erweiterte die Fähigkeiten der abstrakten Maschine auf Parallelität.


Die Behauptung, dass die abstrakte C-Maschine seit langem nicht mehr mit der Struktur moderner Prozessoren vereinbar ist, betrifft nicht so sehr eine bestimmte Sprache, sondern Computer, die die von Neumann-Architektur verwenden, auch in paralleler Ausführung.


Als Praktiker möchte ich jedoch Folgendes erwähnen: Wir können davon ausgehen, dass der Fonneimann-Ansatz veraltet ist, wir können davon ausgehen, dass er relevant ist, aber dies hebt nicht die Tatsache auf, dass die heutigen populären Allzweckarchitekturen Ableitungen der traditionellen Ansätze verwenden.


Die standardisierte und tragbare Verkörperung der von Neumann-Architektur - die abstrakte C-Maschine - ist bequem auf allen wichtigen Plattformen implementiert und erfreut sich daher zu Recht ihrer Beliebtheit als tragbarer Assembler.


Optimierung von Compilern und Low-Level-Sprache


Unsere zweite Voraussetzung für eine Low-Level-Sprache ist der Zugriff auf die Low-Level-Implementierungsdetails jeder der gängigen Plattformen. Im Fall von C ist dies eine direkte Arbeit mit Speicher und Objekten darin als Array von Bytes, die Fähigkeit, direkt mit Byteadressen zu arbeiten, und eine erweiterte Zeigerarithmetik.


C-Kritiker weisen darauf hin, dass der Sprachstandard zu viele Garantien gibt, beispielsweise hinsichtlich der Position einzelner Felder in Strukturen und Assoziationen. Zusammen mit Zeigern und primitiven Mechanismen von Schleifen erschwert dies die Arbeit des Optimierers.


In der Tat würde ein deklarativerer Ansatz es dem Compiler ermöglichen, die Probleme der Datenausrichtung im Speicher oder der optimalen Reihenfolge der Felder in Strukturen unabhängig zu lösen. und übergeordnete Zyklen bieten die Freiheit, die Sie beim Vektorisieren benötigen.


Die Position der C-Entwickler ist in diesem Fall wie folgt: Eine Sprache auf niedriger Ebene sollte es ermöglichen, auf einer Ebene zu arbeiten, die niedrig genug ist, damit der Programmierer Optimierungsprobleme unabhängig lösen kann. Innerhalb von C ist es möglich, als Compiler zu arbeiten, beispielsweise SIMD-Anweisungen auszuwählen und die Daten korrekt im Speicher abzulegen.


Mit anderen Worten, unser Erfordernis des Zugriffs auf Implementierungsdetails jeder Plattform steht im Widerspruch zu den Wünschen der Entwickler, Compiler gerade aufgrund des Vorhandenseins von Tools auf niedriger Ebene zu optimieren.


Interessanterweise behauptet Chiznell in einem Artikel mit dem Titel „C ist keine Sprache auf niedriger Ebene“ paradoxerweise, dass C zu niedrig ist, was auf das Fehlen von Werkzeugen auf hoher Ebene hinweist. Praktiker benötigen jedoch genau Tools auf niedriger Ebene, da die Sprache sonst nicht zur Entwicklung von Betriebssystemen und anderen Programmen auf niedriger Ebene verwendet werden kann, dh die zweite unserer Anforderungen nicht erfüllt.


Um von der Beschreibung der Optimierungsprobleme, nämlich C, abzulenken, möchte ich darauf hinweisen, dass derzeit nicht weniger Aufwand in die Optimierung von Compilern von Hochsprachen (das gleiche C # und Java) investiert wird als in GCC oder Clang. Funktionale Sprachen haben auch genügend effektive Compiler: MLTon, OCaml und andere. Aber die Entwickler desselben OCaml können sich bestenfalls einer Leistung rühmen, die der halben Geschwindigkeit von C-Code entspricht ...


Standard als absolutes Gut


In seinem Artikel zitiert Chiznell die Ergebnisse einer 2015 durchgeführten Umfrage : Viele Programmierer haben Fehler bei der Lösung von Problemen beim Verständnis der C-Standards gemacht.


Ich nehme an, einer der Leser hat sich mit dem C-Standard befasst. Ich habe eine Papierversion von C99, 900 Seiten Werbung. Dies ist keine lakonische Schemaspezifikation mit einem Volumen von weniger als 100 Seiten und keinem geleckten Standard-ML von 300. Arbeitsvergnügen Niemand bekommt den C-Standard: weder Compiler-Entwickler noch Dokumententwickler noch Programmierer.


Wir müssen jedoch verstehen, dass der C-Standard nachträglich entwickelt wurde, nachdem viele "fast kaum Orte" -kompatible Dialekte aufgetaucht waren. ANSI C-Autoren haben großartige Arbeit geleistet, indem sie vorhandene Implementierungen zusammengefasst und mit unzähligen „Krücken“ von Unorthogonalität im Sprachdesign abgedeckt haben.


Es mag seltsam erscheinen, dass sich jemand verpflichtet hat, ein solches Dokument zu implementieren. Aber C wurde von vielen Compilern implementiert. Ich werde die Geschichten anderer über den Zoo der UNIX-Welt der späten 80er Jahre nicht nacherzählen, zumal ich es damals selbst nicht sehr sicher und nur bis fünf Uhr betrachtete. Aber natürlich brauchte jeder in der Branche wirklich einen Standard.


Das Tolle ist, dass es existiert und von mindestens drei großen Compilern und vielen kleineren Compilern implementiert wird, die zusammen Hunderte von Plattformen unterstützen. Keine der Konkurrenzsprachen C, die die Krone des Königs der niederen Sprachen beanspruchen, kann sich einer solchen Vielfalt und Vielseitigkeit rühmen.


Eigentlich ist der aktuelle C-Standard nicht so schlecht. Ein mehr oder weniger erfahrener Programmierer ist in der Lage, in angemessener Zeit einen nicht optimierenden C-Compiler zu entwickeln, was durch die Existenz vieler Semi-Amateur-Implementierungen (dieselbe TCC, LCC und 8cc) bestätigt wird.


Ein allgemein anerkannter Standard bedeutet, dass C die letzte unserer Anforderungen für eine einfache Sprache erfüllt: Diese Sprache basiert auf einer Spezifikation, nicht auf einer bestimmten Implementierung.


Alternative Architekturen - Spezialcomputer


Lifewell führt jedoch ein anderes Argument an und kehrt zum Gerät moderner Allzweckprozessoren zurück, die von Neumann-Architekturoptionen implementieren. Er behauptet, es sei sinnvoll, die Prinzipien des Zentralprozessors zu ändern. Diese Kritik bezieht sich wiederum nicht auf C, sondern auf das grundlegendste Modell der imperativen Programmierung.


In der Tat gibt es viele Alternativen zum traditionellen Ansatz mit sequentieller Ausführung von Programmen: SIMD-Modelle im GPU-Stil, Modelle im Stil einer abstrakten Erlang-Maschine und andere. Jeder dieser Ansätze ist jedoch nur begrenzt anwendbar, wenn er in einem Zentralprozessor verwendet wird.


GPUs beispielsweise multiplizieren Matrizen in Spielen und beim maschinellen Lernen bemerkenswert, sind jedoch für die Raytracing-Funktion schwierig zu verwenden. Mit anderen Worten, dieses Modell ist für spezialisierte Beschleuniger geeignet, funktioniert jedoch nicht für Allzweckprozessoren.


Erlang funktioniert gut in einem Cluster, aber eine effiziente schnelle Sortierung oder schnelle Hash-Tabelle ist schwierig. Das Modell unabhängiger Akteure wird besser auf einer höheren Ebene in einem großen Cluster verwendet, in dem jeder Knoten immer noch dieselbe Hochleistungsmaschine mit einem herkömmlichen Prozessor ist.


Inzwischen haben moderne x86-kompatible Prozessoren lange Zeit eine Reihe von Vektorbefehlen enthalten, die in Zweck und Funktionsprinzipien der GPU ähnlich sind, aber die allgemeine Prozessorschaltung im von Neumann-Stil als Ganzes beibehalten. Ich habe keinen Zweifel daran, dass in allgemeinen Prozessoren ziemlich allgemeine Computeransätze enthalten sein werden.


Es gibt eine so maßgebliche Meinung: Die Zukunft liegt in speziellen programmierbaren Beschleunigern. Unter solch außergewöhnlichen Eisenstücken ist es wirklich sinnvoll, Sprachen mit besonderer Semantik zu entwickeln. Ein Allzweckcomputer war und ist jedoch dem PDP-11 ähnlich, für den C-ähnliche Imperativsprachen so gut geeignet sind.


C wird leben


In Chiznells Artikel gibt es einen grundsätzlichen Widerspruch. Er schreibt, dass Prozessoren die abstrakte C-Maschine (und den längst vergessenen PDP-11) nachahmen, um die Geschwindigkeit von C-Programmen sicherzustellen, woraufhin sie auf die Einschränkungen einer solchen Maschine hinweisen. Aber ich verstehe nicht, warum dies bedeutet, dass "C keine einfache Sprache ist".


Im Allgemeinen geht es hier nicht um die Mängel von C als Sprache, sondern um Kritik an gängigen Architekturen im Neumann-Stil und dem daraus folgenden Programmiermodell. Bisher scheint die Branche jedoch nicht bereit zu sein, die bekannte Architektur aufzugeben (zumindest nicht bei Allzweckprozessoren).


Trotz der Verfügbarkeit vieler spezialisierter Prozessoren wie GPUs und TPUs regiert die von Neumann-Architektur derzeit den Ball und die Branche benötigt eine Sprache, die es ihr ermöglicht, im Rahmen der beliebtesten Architektur auf der niedrigstmöglichen Ebene zu arbeiten. Eine ziemlich einfache, auf Dutzende von Plattformen portierte und standardisierte Programmiersprache ist C (und seine unmittelbare Familie).


Trotzdem hat C genug Mängel: eine archaische Funktionsbibliothek, einen komplizierten und inkonsistenten Standard und grobe Designfehler. Aber anscheinend haben die Schöpfer der Sprache immer noch etwas richtig gemacht.


Auf die eine oder andere Weise brauchen wir immer noch eine einfache Sprache, die speziell für beliebte Fonneimann-Computer entwickelt wurde. Und lassen Sie C veraltet sein, aber anscheinend muss jeder Nachfolger immer noch auf denselben Prinzipien aufbauen.

Source: https://habr.com/ru/post/de465429/


All Articles