Warum die Programmiersprache C immer noch regiert

Keine Technologie bleibt 50 Jahre lang bestehen, es sei denn, sie erledigt ihre Arbeit besser als die meisten anderen - insbesondere eine Computertechnologie. Die Programmiersprache C ist seit 1972 lebendig und erfolgreich und gilt immer noch als einer der Grundbausteine ​​unserer softwaredefinierten Welt.

Aber manchmal bleibt eine Technologie bestehen, weil die Leute einfach nicht dazu gekommen sind, sie zu ersetzen. In den letzten Jahrzehnten sind Dutzende anderer Sprachen aufgetaucht - einige wurden explizit entwickelt, um Cs Dominanz herauszufordern, andere haben C als Nebenprodukt ihrer Popularität von der Seite gestrichen.

Es ist nicht schwer zu argumentieren, dass C ersetzt werden muss. Programmiersprachenforschung und Softwareentwicklungspraktiken weisen alle darauf hin, dass es weitaus bessere Möglichkeiten gibt, Dinge zu tun als C. Trotzdem bleibt C bestehen, mit jahrzehntelanger Forschung und Entwicklung. Nur wenige andere Sprachen können es hinsichtlich Leistung, Bare-Metal-Kompatibilität oder Allgegenwart übertreffen. Trotzdem ist es sehenswert, wie sich C 2018 gegen den Wettbewerb um namhafte Sprachen behaupten kann.

C gegen C ++

Natürlich wird C am häufigsten mit C ++ verglichen, der Sprache, die - wie der Name schon sagt - als Erweiterung von C erstellt wurde. Die Unterschiede zwischen C ++ und C können je nach Fragesteller als umfangreich oder übermäßig charakterisiert werden  .

Obwohl C ++ in seiner Syntax und Vorgehensweise immer noch C-ähnlich ist, bietet es viele wirklich nützliche Funktionen, die in C nicht nativ verfügbar sind: Namespaces, Vorlagen, Ausnahmen, automatische Speicherverwaltung usw. Projekte, die erstklassige Leistung erfordern - Datenbanken, maschinelle Lernsysteme -, werden häufig in C ++ geschrieben und verwenden diese Funktionen, um jeden Leistungsabfall aus dem System herauszuholen.

Darüber hinaus expandiert C ++ weiterhin viel aggressiver als C. Das bevorstehende C ++ 20 bringt noch mehr auf den Tisch, einschließlich Modulen, Coroutinen, einer Synchronisationsbibliothek und Konzepten, die die Verwendung von Vorlagen vereinfachen. Die neueste Überarbeitung des C-Standards fügt wenig hinzu und konzentriert sich auf die Beibehaltung der Abwärtskompatibilität.

Alle Pluspunkte in C ++ können auch als Minuspunkte verwendet werden. Grosse. Je mehr C ++ - Funktionen Sie verwenden, desto komplexer wird es und desto schwieriger wird es, die Ergebnisse zu zähmen. Entwickler, die sich auf eine Teilmenge von C ++ beschränken, können viele der schlimmsten Fallstricke und Exzesse vermeiden. Einige Geschäfte möchten sich jedoch vor der Komplexität von C ++ schützen. Das Festhalten an C zwingt Entwickler, sich auf diese Teilmenge zu beschränken. Das Linux-Kernel-Entwicklungsteam verzichtet beispielsweise auf C ++.

Die Auswahl von C über C ++ ist eine Möglichkeit für Sie - und alle Entwickler, die den Code nach Ihnen pflegen - zu vermeiden, dass Sie sich mit C ++ - Exzessen verwickeln müssen, indem Sie einen erzwungenen Minimalismus akzeptieren. Natürlich bietet C ++ aus gutem Grund eine Vielzahl von Funktionen auf hoher Ebene. Aber wenn Minimalismus eine bessere Passform für aktuelle und zukünftige Projekte ist-und Projektteams -dann C macht mehr Sinn.

C gegen Java

Nach Jahrzehnten bleibt Java ein Grundnahrungsmittel für die Entwicklung von Unternehmenssoftware - und allgemein ein Grundnahrungsmittel für die Entwicklung. Viele der wichtigsten Unternehmenssoftwareprojekte wurden in Java geschrieben - einschließlich der überwiegenden Mehrheit der Apache Software Foundation-Projekte - und Java bleibt eine brauchbare Sprache für die Entwicklung neuer Projekte mit Anforderungen für Unternehmen.

Die Java-Syntax leiht sich viel von C und C ++ aus. Im Gegensatz zu C kompiliert Java jedoch nicht standardmäßig in nativen Code. Stattdessen kompiliert die Java-Laufzeitumgebung, die JVM, JIT (Just-in-Time) Java-Code, um in der Zielumgebung ausgeführt zu werden. Unter den richtigen Umständen kann sich JITted Java-Code der Leistung von C annähern oder diese sogar übertreffen.

Die Philosophie „Einmal schreiben, überall ausführen“ hinter Java ermöglicht es Java-Programmen auch, mit relativ geringem Aufwand für eine Zielarchitektur ausgeführt zu werden. Obwohl C auf sehr viele Architekturen portiert wurde, muss ein bestimmtes C-Programm möglicherweise noch angepasst werden, damit es beispielsweise unter Windows oder Linux ordnungsgemäß ausgeführt werden kann.

Diese Kombination aus Portabilität und starker Leistung sowie ein riesiges Ökosystem von Softwarebibliotheken und Frameworks machen Java zu einer bevorzugten Sprache und Laufzeit für die Erstellung von Unternehmensanwendungen.

Wo Java hinter C zurückbleibt, ist ein Bereich, in dem Java niemals konkurrieren sollte: in der Nähe des Metalls laufen oder direkt mit Hardware arbeiten. C-Code wird zu Maschinencode kompiliert, der vom Prozess direkt ausgeführt wird. Java wird in Bytecode kompiliert, bei dem es sich um Zwischencode handelt, den der JVM-Interpreter dann in Maschinencode konvertiert. Obwohl die automatische Speicherverwaltung von Java in den meisten Fällen ein Segen ist, eignet sich C besser für Programme, die begrenzte Speicherressourcen optimal nutzen müssen.

Es gibt jedoch einige Bereiche, in denen Java in Bezug auf die Geschwindigkeit C nahe kommen kann. Die JIT-Engine der JVM optimiert Routinen zur Laufzeit basierend auf dem Programmverhalten und ermöglicht viele Optimierungsklassen, die mit vorab kompiliertem C nicht möglich sind. Während die Java-Laufzeit die Speicherverwaltung automatisiert, umgehen einige neuere Anwendungen dies. Beispielsweise optimiert Apache Spark die speicherinterne Verarbeitung teilweise durch Verwendung von benutzerdefiniertem Speicherverwaltungscode, der die JVM umgeht.

C vs. C # und .Net

Fast zwei Jahrzehnte nach ihrer Einführung bleiben C # und das .Net Framework wichtige Bestandteile der Welt der Unternehmenssoftware. Es wurde gesagt, dass C # und .Net die Antwort von Microsoft auf Java waren - ein Compiler-System für verwalteten Code und universelle Laufzeit - und so viele Vergleiche zwischen C und Java gelten auch für C und C # /. Net.

Wie Java (und in gewissem Maße Python) bietet .Net Portabilität auf einer Vielzahl von Plattformen und einem riesigen Ökosystem integrierter Software. Dies sind keine geringen Vorteile, wenn man bedenkt, wie viel unternehmensorientierte Entwicklung in der .Net-Welt stattfindet. Wenn Sie ein Programm in C # oder einer anderen .NET-Sprache entwickeln, können Sie auf ein Universum von Tools und Bibliotheken zurückgreifen, die für die .NET-Laufzeit geschrieben wurden. 

Ein weiterer Java-ähnlicher .NET-Vorteil ist die JIT-Optimierung. C # - und .Net-Programme können gemäß C vorab kompiliert werden, sie werden jedoch hauptsächlich just-in-time von der .Net-Laufzeit kompiliert und mit Laufzeitinformationen optimiert. Die JIT-Kompilierung ermöglicht alle Arten von direkten Optimierungen für ein laufendes .NET-Programm, das in C nicht ausgeführt werden kann.

Wie C bieten C # und .Net verschiedene Mechanismen für den direkten Zugriff auf den Speicher. Auf Heap-, Stack- und nicht verwalteten Systemspeicher kann über .NET-APIs und -Objekte zugegriffen werden. Entwickler können den unsafeModus in .Net verwenden, um eine noch höhere Leistung zu erzielen.

Nichts davon ist jedoch kostenlos. Verwaltete Objekte und unsafeObjekte können nicht willkürlich ausgetauscht werden, und das Marshalling zwischen ihnen ist mit Leistungskosten verbunden. Um die Leistung von .NET-Anwendungen zu maximieren, muss die Bewegung zwischen verwalteten und nicht verwalteten Objekten auf ein Minimum beschränkt werden.

Wenn Sie es sich nicht leisten können, die Strafe für verwalteten oder nicht verwalteten Speicher zu zahlen, oder wenn die .NET-Laufzeit eine schlechte Wahl für die Zielumgebung ist (z. B. Kernelspeicher) oder möglicherweise überhaupt nicht verfügbar ist, dann ist C das, was Sie tun brauchen. Und im Gegensatz zu C # und .Net entsperrt C standardmäßig den direkten Speicherzugriff. 

C gegen Go

Die Go-Syntax hat viel mit C zu tun - geschweifte Klammern als Trennzeichen, mit Semikolons abgeschlossene Anweisungen usw. Entwickler, die sich mit C auskennen, können in der Regel ohne große Schwierigkeiten direkt in Go einsteigen, selbst wenn sie neue Go-Funktionen wie Namespaces und Paketverwaltung berücksichtigen.

Lesbarer Code war eines der wichtigsten Designziele von Go: Machen Sie es Entwicklern einfach, sich mit jedem Go-Projekt vertraut zu machen und sich in kurzer Zeit mit der Codebasis vertraut zu machen. C-Codebasen können schwer zu verstehen sein, da sie dazu neigen, sich in ein Makronest einer Ratte zu verwandeln #ifdef, das sowohl für ein Projekt als auch für ein bestimmtes Team spezifisch ist. Die Syntax von Go und die integrierten Tools zur Code-Formatierung und zum Projektmanagement sollen solche institutionellen Probleme in Schach halten.

Go bietet außerdem Extras wie Goroutinen und Kanäle, Tools auf Sprachebene für den Umgang mit Parallelität und Nachrichtenübermittlung zwischen Komponenten. C würde erfordern, dass solche Dinge von Hand gerollt oder von einer externen Bibliothek bereitgestellt werden, aber Go stellt sie sofort bereit, was es viel einfacher macht, Software zu erstellen, die sie benötigt.

Wo Go sich am meisten von C unter der Haube unterscheidet, liegt in der Speicherverwaltung. Go-Objekte werden standardmäßig automatisch verwaltet und Müll gesammelt. Für die meisten Programmierjobs ist dies äußerst praktisch. Es bedeutet aber auch, dass jedes Programm, das eine deterministische Behandlung des Speichers erfordert, schwieriger zu schreiben ist.

Go enthält das unsafePaket zum Umgehen einiger Sicherheitsmaßnahmen für die Typverarbeitung von Go, z. B. zum Lesen und Schreiben eines beliebigen Speichers mit einem PointerTyp. Es unsafewird jedoch eine Warnung angezeigt, dass damit geschriebene Programme „möglicherweise nicht portierbar sind und nicht durch die Go 1-Kompatibilitätsrichtlinien geschützt sind“.

Go eignet sich gut zum Erstellen von Programmen wie Befehlszeilenprogrammen und Netzwerkdiensten, da diese selten so feinkörnige Manipulationen erfordern. Low-Level-Gerätetreiber, Kernel-Space-Betriebssystemkomponenten und andere Aufgaben, die eine genaue Kontrolle über das Speicherlayout und die Speicherverwaltung erfordern, werden am besten in C erstellt.

C gegen Rust

In gewisser Weise ist Rust eine Antwort auf die von C und C ++ verursachten Probleme bei der Speicherverwaltung und auf viele andere Mängel dieser Sprachen. Rust wird zu nativem Maschinencode kompiliert, sodass es hinsichtlich der Leistung mit C vergleichbar ist. Die Speichersicherheit ist jedoch standardmäßig das Hauptverkaufsargument von Rust.

Die Syntax- und Kompilierungsregeln von Rust helfen Entwicklern, häufige Speicherverwaltungsfehler zu vermeiden. Wenn ein Programm ein Speicherverwaltungsproblem hat, das die Rust-Syntax überschreitet, wird es einfach nicht kompiliert. Neulinge in der Sprache, insbesondere aus einer Sprache wie C, die viel Platz für solche Fehler bietet, verbringen die erste Phase ihrer Rust-Ausbildung damit, zu lernen, wie man den Compiler beschwichtigt. Aber Rust-Befürworter argumentieren, dass sich dieser kurzfristige Schmerz langfristig auszahlt: sicherer Code, der die Geschwindigkeit nicht beeinträchtigt.

Rost verbessert auch C mit seinen Werkzeugen. Das Projekt- und Komponentenmanagement ist Teil der Toolchain, die standardmäßig mit Rust geliefert wird, genau wie mit Go. Es gibt eine empfohlene Standardmethode zum Verwalten von Paketen, zum Organisieren von Projektordnern und zum Behandeln vieler anderer Dinge, die in C bestenfalls ad-hoc sind, wobei jedes Projekt und Team sie unterschiedlich behandelt.

Was in Rust als Vorteil angepriesen wird, scheint einem C-Entwickler jedoch nicht so zu sein. Die Sicherheitsfunktionen zur Kompilierungszeit von Rust können nicht deaktiviert werden, daher muss selbst das trivialste Rust-Programm den Sicherheitsbestimmungen von Rust entsprechen. C ist standardmäßig weniger sicher, aber bei Bedarf viel flexibler und fehlerverzeihender.

Ein weiterer möglicher Nachteil ist die Größe der Rust-Sprache. C hat relativ wenige Funktionen, selbst wenn die Standardbibliothek berücksichtigt wird. Das Rust-Feature-Set ist weitläufig und wächst weiter. Wie bei C ++ bedeutet der größere Rust-Funktionsumfang mehr Leistung, aber auch mehr Komplexität. C ist eine kleinere Sprache, aber mental viel einfacher zu modellieren, daher vielleicht besser für Projekte geeignet, bei denen Rust übertrieben wäre.

C gegen Python

Heutzutage scheint Python immer ins Gespräch zu kommen, wenn es um Softwareentwicklung geht. Schließlich ist Python „die zweitbeste Sprache für alles“ und zweifellos eine der vielseitigsten mit Tausenden von Bibliotheken von Drittanbietern.

Was Python betont und wo es sich am meisten von C unterscheidet, ist die Bevorzugung der Entwicklungsgeschwindigkeit gegenüber der Ausführungsgeschwindigkeit. Ein Programm, dessen Zusammenstellung in einer anderen Sprache - wie C - eine Stunde dauern kann, kann in wenigen Minuten in Python zusammengestellt werden. Auf der anderen Seite kann die Ausführung dieses Programms in C Sekunden dauern, in Python jedoch eine Minute. (Eine gute Faustregel: Python-Programme laufen im Allgemeinen um eine Größenordnung langsamer als ihre C-Gegenstücke.) Für viele Jobs auf moderner Hardware ist Python jedoch schnell genug, und das war der Schlüssel zu seiner Einführung.

Ein weiterer wichtiger Unterschied ist die Speicherverwaltung. Python-Programme werden vollständig von der Python-Laufzeit speicherverwaltet, sodass sich Entwickler keine Gedanken über das Zuweisen und Freigeben von Speicher machen müssen. Aber auch hier geht die Benutzerfreundlichkeit zu Lasten der Laufzeitleistung. Das Schreiben von C-Programmen erfordert sorgfältige Beachtung der Speicherverwaltung, aber die daraus resultierenden Programme sind häufig der Goldstandard für reine Maschinengeschwindigkeit.

Unter der Haut haben Python und C jedoch eine tiefe Verbindung: Die Referenz-Python-Laufzeit ist in C geschrieben. Dadurch können Python-Programme in C und C ++ geschriebene Bibliotheken umbrechen. Wichtige Teile des Python-Ökosystems von Bibliotheken von Drittanbietern, z. B. für maschinelles Lernen, enthalten C-Code im Kern.

Wenn die Entwicklungsgeschwindigkeit wichtiger ist als die Ausführungsgeschwindigkeit und wenn die meisten performanten Teile des Programms in eigenständige Komponenten isoliert werden können (anstatt im gesamten Code verteilt zu sein), wird entweder reines Python oder eine Mischung aus Python- und C-Bibliotheken erstellt eine bessere Wahl als C allein. Ansonsten regiert C immer noch.