Ich lese K & Rs „The C Programming Language“ und bin auf diese Aussage gestoßen [Einleitung, S. 3]:
Da die von C bereitgestellten Datentypen und Steuerungsstrukturen von den meisten Computern direkt unterstützt werden , ist die zur Implementierung eigenständiger Programme erforderliche Laufzeitbibliothek winzig.
Was bedeutet die fettgedruckte Aussage? Gibt es ein Beispiel für einen Datentyp oder eine Kontrollstruktur, die nicht direkt von einem Computer unterstützt wird?
Antworten:
Ja, es gibt Datentypen, die nicht direkt unterstützt werden.
Auf vielen eingebetteten Systemen gibt es keine Hardware-Gleitkommaeinheit. Wenn Sie also Code wie folgt schreiben:
Es wird in so etwas übersetzt:
Dann muss der Compiler oder die Standardbibliothek eine Implementierung von bereitstellen
_float_add()
, die Speicher auf Ihrem eingebetteten System belegt. Wenn Sie auf einem wirklich winzigen System Bytes zählen, kann sich dies summieren.Ein weiteres häufiges Beispiel sind 64-Bit-Ganzzahlen (
long long
im C-Standard seit 1999), die von 32-Bit-Systemen nicht direkt unterstützt werden. Alte SPARC-Systeme unterstützten keine ganzzahlige Multiplikation, daher musste die Multiplikation zur Laufzeit bereitgestellt werden. Es gibt andere Beispiele.Andere Sprachen
Im Vergleich dazu haben andere Sprachen kompliziertere Grundelemente.
Zum Beispiel erfordert ein Lisp-Symbol viel Laufzeitunterstützung, genau wie Tabellen in Lua, Zeichenfolgen in Python, Arrays in Fortran usw. Die entsprechenden Typen in C sind normalerweise entweder überhaupt nicht Teil der Standardbibliothek (keine Standardsymbole oder -tabellen) oder sie sind viel einfacher und erfordern nicht viel Laufzeitunterstützung (Arrays in C sind im Grunde nur Zeiger, nicht terminierte Zeichenfolgen) fast so einfach).
Kontrollstrukturen
Eine bemerkenswerte Kontrollstruktur, die in C fehlt, ist die Ausnahmebehandlung. Der nichtlokale Exit ist auf
setjmp()
und beschränktlongjmp()
, wodurch nur bestimmte Teile des Prozessorstatus gespeichert und wiederhergestellt werden. Im Vergleich dazu muss die C ++ - Laufzeit den Stapel durchlaufen und Destruktoren und Ausnahmebehandlungsroutinen aufrufen.quelle
Eigentlich wette ich, dass sich der Inhalt dieser Einführung seit 1978, als Kernighan und Ritchie sie zum ersten Mal in der ersten Ausgabe des Buches geschrieben haben, nicht viel geändert hat und sie sich mehr als modern auf die Geschichte und Entwicklung von C zu dieser Zeit beziehen Implementierungen.
Computer sind im Grunde nur Speicherbänke und Zentralprozessoren, und jeder Prozessor arbeitet mit einem Maschinencode. Teil des Entwurfs jedes Prozessors ist eine Befehlssatzarchitektur, die als Assemblersprache bezeichnet wird und eine Eins-zu-Eins-Zuordnung von einer Reihe von für Menschen lesbaren Mnemoniken zu Maschinencode darstellt, bei dem es sich ausschließlich um Zahlen handelt.
Die Autoren der C-Sprache - und der unmittelbar davor stehenden B- und BCPL-Sprachen - wollten Konstrukte in der Sprache definieren, die so effizient wie möglich in Assembly kompiliert wurden. Tatsächlich waren sie durch Einschränkungen im Ziel dazu gezwungen Hardware. Wie andere Antworten gezeigt haben, umfasste dies Verzweigungen (GOTO und andere Flusssteuerung in C), Verschiebungen (Zuweisung), logische Operationen (& | ^), Grundrechenarten (Addieren, Subtrahieren, Inkrementieren, Dekrementieren) und Speicheradressierung (Zeiger) ). Ein gutes Beispiel sind die Operatoren vor / nach dem Inkrementieren und Dekrementieren in C, die angeblich von Ken Thompson zur B-Sprache hinzugefügt wurden, weil sie nach der Kompilierung direkt in einen einzelnen Opcode übersetzen konnten.
Dies meinten die Autoren, als sie sagten "direkt von den meisten Computern unterstützt". Sie bedeuteten nicht, dass andere Sprachen Typen und Strukturen enthielten, die nicht direkt unterstützt wurden - sie bedeuteten, dass durch Design C-Konstrukte am direktesten (manchmal buchstäblich direkt) in Assembly übersetzt wurden.
Diese enge Beziehung zur zugrunde liegenden Assembly, die immer noch alle für die strukturierte Programmierung erforderlichen Elemente enthält, führte zu Cs frühzeitiger Einführung und hat C heute in Umgebungen, in denen die Effizienz des kompilierten Codes immer noch von entscheidender Bedeutung ist, zu einer beliebten Sprache gemacht.
Eine interessante Zusammenfassung der Geschichte der Sprache finden Sie unter Die Entwicklung der C-Sprache - Dennis Ritchie
quelle
Die kurze Antwort lautet: Die meisten von C unterstützten Sprachkonstrukte werden auch vom Mikroprozessor des Zielcomputers unterstützt. Daher wird kompilierter C-Code sehr gut und effizient in die Assemblersprache des Mikroprozessors übersetzt, was zu kleinerem Code und geringerem Platzbedarf führt.
Die längere Antwort erfordert ein wenig Assembler-Kenntnisse. In C eine Aussage wie diese:
würde in der Montage so etwas übersetzen:
Vergleichen Sie dies mit etwas wie C ++:
Der resultierende Assembler-Code (abhängig von der Größe von MyClass ()) kann zu Hunderten von Assembler-Zeilen führen.
Ohne Programme in Assemblersprache zu erstellen, ist reines C wahrscheinlich der "dünnste" und "engste" Code, in dem Sie ein Programm erstellen können.
BEARBEITEN
Angesichts der Kommentare zu meiner Antwort beschloss ich, einen Test durchzuführen, nur aus Gründen meiner eigenen Gesundheit. Ich habe ein Programm namens "test.c" erstellt, das so aussah:
Ich habe dies bis zur Assemblierung mit gcc kompiliert. Ich habe die folgende Befehlszeile verwendet, um es zu kompilieren:
Hier ist die resultierende Assemblersprache:
Ich erstelle dann eine Datei namens "test.cpp", die eine Klasse definiert und dasselbe wie "test.c" ausgibt:
Ich habe es auf die gleiche Weise mit diesem Befehl kompiliert:
Hier ist die resultierende Assembly-Datei:
Wie Sie deutlich sehen können, ist die resultierende Assembly-Datei in der C ++ - Datei viel größer als in der C-Datei. Selbst wenn Sie alle anderen Dinge ausschneiden und nur das C "main" mit dem C ++ "main" vergleichen, gibt es viele zusätzliche Dinge.
quelle
MyClass myClass { 10 }
in C ++ wird sehr wahrscheinlich zu genau derselben Assembly kompiliert. Moderne C ++ - Compiler haben die Abstraktionsstrafe beseitigt. Infolgedessen können sie häufig C-Compiler schlagen. ZB ist die Abstraktionsstrafe in Csqsort
real, aber C ++std::sort
hat auch nach einer grundlegenden Optimierung keine Abstraktionsstrafe.K & R bedeutet, dass die meisten C-Ausdrücke (technische Bedeutung) einer oder mehreren Assembly-Anweisungen zugeordnet sind, nicht einem Funktionsaufruf einer Support-Bibliothek. Die üblichen Ausnahmen sind die Ganzzahldivision auf Architekturen ohne Hardware-Div-Anweisung oder Gleitkomma auf Maschinen ohne FPU.
Es gibt ein Zitat:
( hier zu finden . Ich dachte, ich erinnere mich an eine andere Variante, wie "Geschwindigkeit der Assemblersprache mit der Bequemlichkeit und Ausdruckskraft der Assemblersprache".)
long int hat normalerweise die gleiche Breite wie die nativen Maschinenregister.
Einige übergeordnete Sprachen definieren die genaue Breite ihrer Datentypen, und Implementierungen auf allen Computern müssen gleich funktionieren. Nicht C.
Wenn Sie mit 128-Bit-Ints auf x86-64 oder im allgemeinen Fall mit BigInteger beliebiger Größe arbeiten möchten, benötigen Sie eine Funktionsbibliothek. Alle CPUs verwenden jetzt das 2s-Komplement als binäre Darstellung negativer Ganzzahlen, aber selbst das war damals beim Entwurf von C nicht der Fall. (Aus diesem Grund sind einige Dinge, die auf Maschinen ohne 2s-Komplement zu unterschiedlichen Ergebnissen führen würden, in den C-Standards technisch nicht definiert.)
C-Zeiger auf Daten oder Funktionen funktionieren genauso wie Baugruppenadressen.
Wenn Sie nachgezählte Referenzen wünschen, müssen Sie dies selbst tun. Wenn Sie virtuelle C ++ - Elementfunktionen wünschen, die eine andere Funktion aufrufen, je nachdem, auf welche Art von Objekt Ihr Zeiger zeigt, muss der C ++ - Compiler viel mehr als nur eine generieren
call
Anweisung mit einer festen Adresse .Strings sind nur Arrays
Außerhalb der Bibliotheksfunktionen werden nur Zeichenfolgen gelesen / geschrieben. Kein Concat, kein Teilstring, keine Suche. (Strings werden als nicht terminiert gespeichert (
'\0'
) Arrays mit 8-Bit-Ganzzahlen und nicht mit Zeiger + Länge Um einen Teilstring zu erhalten, müssen Sie einen nul in den ursprünglichen String schreiben.)CPUs haben manchmal Befehle, die für die Verwendung durch eine Zeichenfolgensuchfunktion ausgelegt sind, verarbeiten jedoch normalerweise immer noch ein Byte pro ausgeführtem Befehl in einer Schleife. (oder mit dem Präfix x86 rep. Wenn C auf x86 entwickelt wurde, wäre das Suchen oder Vergleichen von Zeichenfolgen möglicherweise eher eine native Operation als ein Funktionsaufruf der Bibliothek.)
Viele andere Antworten geben Beispiele für Dinge, die nicht nativ unterstützt werden, wie Ausnahmebehandlung, Hash-Tabellen, Listen. Die Designphilosophie von K & R ist der Grund, warum C keine davon nativ hat.
quelle
Die Assemblersprache eines Prozesses befasst sich im Allgemeinen mit Springen (Gehe zu), Anweisungen, Verschieben von Anweisungen, binären arthritischen (XOR, NAND, UND ODER usw.), Speicherfeldern (oder Adressen). Kategorisiert den Speicher in zwei Typen: Anweisung und Daten. Das ist ungefähr alles, was eine Assemblersprache ist (ich bin sicher, Assembler-Programmierer werden argumentieren, dass mehr dahinter steckt, aber es läuft im Allgemeinen darauf hinaus). C ähnelt stark dieser Einfachheit.
C soll zusammensetzen, was Algebra für Arithmetik ist.
C kapselt die Grundlagen der Assemblierung (die Sprache des Prozessors). Ist wahrscheinlich eine wahrere Aussage als "Weil die von C bereitgestellten Datentypen und Kontrollstrukturen direkt von den meisten Computern unterstützt werden"
quelle
Hüten Sie sich vor irreführenden Vergleichen
quelle
Alle grundlegenden Datentypen und ihre Operationen in der Sprache C können durch eine oder mehrere maschinensprachliche Anweisungen ohne Schleife implementiert werden - sie werden direkt von der (praktisch jeder) CPU unterstützt.
Einige gängige Datentypen und ihre Operationen erfordern Dutzende von maschinensprachlichen Anweisungen oder das Iterieren einer Laufzeitschleife oder beides.
Viele Sprachen haben eine spezielle abgekürzte Syntax für solche Typen und ihre Operationen. Die Verwendung solcher Datentypen in C erfordert im Allgemeinen die Eingabe von viel mehr Code.
Zu diesen Datentypen und Operationen gehören:
Alle diese Vorgänge erfordern Dutzende von maschinensprachlichen Anweisungen oder das Durchlaufen einer Laufzeitschleife auf nahezu jedem Prozessor.
Einige beliebte Kontrollstrukturen, die auch Dutzende von maschinensprachlichen Anweisungen oder Schleifen erfordern, umfassen:
Unabhängig davon, ob in C oder einer anderen Sprache geschrieben, muss die CPU bei der Bearbeitung solcher Datentypen durch ein Programm eventuell alle Anweisungen ausführen, die zur Bearbeitung dieser Datentypen erforderlich sind. Diese Anweisungen sind häufig in einer "Bibliothek" enthalten. Jede Programmiersprache, auch C, verfügt über eine "Laufzeitbibliothek" für jede Plattform, die standardmäßig in jeder ausführbaren Datei enthalten ist.
Die meisten Leute, die Compiler schreiben, haben die Anweisungen zum Bearbeiten aller Datentypen, die "in die Sprache integriert" sind, in ihre Laufzeitbibliothek aufgenommen. Da C nicht über jede der oben genannten Datentypen und Operationen und Kontrollstrukturen aufgebaut in die die Sprache, keiner von ihnen sind in der C - Laufzeitbibliothek enthalten - , die der C - Laufzeitbibliothek kleiner als die Lauf- macht Zeitbibliothek anderer Programmiersprachen, in die mehr der oben genannten Dinge in die Sprache integriert sind.
Wenn ein Programmierer möchte, dass ein Programm - in C oder einer anderen Sprache seiner Wahl - andere Datentypen manipuliert, die nicht "in die Sprache integriert" sind, weist dieser Programmierer den Compiler im Allgemeinen an, zusätzliche Bibliotheken in dieses Programm aufzunehmen, oder manchmal (um "Abhängigkeiten zu vermeiden") schreibt eine weitere Implementierung dieser Operationen direkt in das Programm.
quelle
Was sind die eingebauten Datentypen
C
? Sie sind Dinge wieint
,char
,* int
,float
, Arrays etc ... Diese Datentypen von der CPU verstanden. Die CPU weiß, wie man mit Arrays arbeitet, wie man Zeiger dereferenziert und wie man mit Zeigern, ganzen Zahlen und Gleitkommazahlen arithmetisch arbeitet.Wenn Sie jedoch zu höheren Programmiersprachen wechseln, haben Sie abstrakte Datentypen und komplexere Konstrukte integriert. Schauen Sie sich zum Beispiel die Vielzahl der integrierten Klassen in der Programmiersprache C ++ an. Die CPU versteht keine Klassen, Objekte oder abstrakten Datentypen, daher schließt die C ++ - Laufzeit die Lücke zwischen der CPU und der Sprache. Dies sind Beispiele für Datentypen, die von den meisten Computern nicht direkt unterstützt werden.
quelle
Das hängt vom Computer ab. Auf dem PDP-11, auf dem C erfunden wurde,
long
wurde es schlecht unterstützt (es gab ein optionales Zusatzmodul, das Sie kaufen konnten und das einige, aber nicht alle 32-Bit-Operationen unterstützte). Das Gleiche gilt in unterschiedlichem Maße für jedes 16-Bit-System, einschließlich des ursprünglichen IBM-PCs. Ebenso für 64-Bit-Operationen auf 32-Bit-Computern oder in 32-Bit-Programmen, obwohl die C-Sprache zum Zeitpunkt des K & R-Buches überhaupt keine 64-Bit-Operationen hatte. Und natürlich gab es in den 80er und 90er Jahren viele Systeme (einschließlich der 386- und 486-Prozessoren) und sogar einige eingebettete Systeme, die Gleitkomma-Arithmetik (float
oderdouble
) nicht direkt unterstützten .Für ein exotischeres Beispiel unterstützen einige Computerarchitekturen nur "wortorientierte" Zeiger (die auf eine Zwei- oder Vier-Byte-Ganzzahl im Speicher zeigen), und Bytezeiger (
char *
odervoid *
) mussten durch Hinzufügen eines zusätzlichen Versatzfelds implementiert werden. Diese Frage geht detailliert auf solche Systeme ein.Die Funktionen der "Laufzeitbibliothek", auf die Bezug genommen wird, sind nicht die, die Sie im Handbuch sehen, sondern Funktionen wie diese in der Laufzeitbibliothek eines modernen Compilers , mit denen die grundlegenden Typoperationen implementiert werden , die vom Computer nicht unterstützt werden . Die Laufzeitbibliothek, auf die sich K & R selbst bezog, finden Sie auf der Website der Unix Heritage Society. Sie können Funktionen wie
ldiv
(im Unterschied zu der gleichnamigen C-Funktion, die zu diesem Zeitpunkt noch nicht vorhanden war) sehen, die zur Implementierung der Division von verwendet wird 32-Bit-Werte, die der PDP-11 selbst mit dem Add-On nicht unterstützte, undcsv
(undcret
auch in csv.c), die Register auf dem Stapel speichern und wiederherstellen, um Aufrufe und Rückgaben von Funktionen zu verwalten.Sie bezogen sich wahrscheinlich auch auf ihre Entscheidung, viele Datentypen, die nicht direkt von der zugrunde liegenden Maschine unterstützt werden, nicht zu unterstützen, im Gegensatz zu anderen modernen Sprachen wie FORTRAN, deren Array-Semantik der zugrunde liegenden Zeigerunterstützung der CPU nicht so gut entsprach wie Cs Arrays. Die Tatsache, dass C-Arrays immer nullindexiert sind und in allen Rängen immer eine bekannte Größe haben, aber das erste bedeutet, dass die Indexbereiche oder -größen der Arrays nicht gespeichert werden müssen und keine Laufzeitbibliotheksfunktionen erforderlich sind, um auf sie zuzugreifen. Der Compiler kann einfach die erforderliche Zeigerarithmetik fest codieren.
quelle
Die Aussage bedeutet einfach, dass die Daten- und Kontrollstrukturen in C maschinenorientiert sind.
Hier sind zwei Aspekte zu berücksichtigen. Zum einen verfügt die C-Sprache über eine Definition (ISO-Standard), die Spielraum für die Definition der Datentypen bietet. Dies bedeutet, dass C-Sprachimplementierungen auf die Maschine zugeschnitten sind . Die Datentypen eines C-Compilers stimmen mit dem überein, was auf dem Computer verfügbar ist, auf den der Compiler abzielt, da die Sprache dafür Spielraum hat. Wenn eine Maschine eine ungewöhnliche Wortgröße wie 36 Bit hat, kann der Typ
int
oderlong
entsprechend angepasst werden. Programme, dieint
genau 32 Bit annehmen, brechen ab.Zweitens gibt es aufgrund solcher Portabilitätsprobleme einen zweiten Effekt. In gewisser Weise ist die Aussage im K & R zu einer Art sich selbst erfüllender Prophezeiung geworden , oder vielleicht umgekehrt. Das heißt, Implementierer neuer Prozessoren sind sich der dringenden Notwendigkeit bewusst, C-Compiler zu unterstützen, und sie wissen, dass es viel C-Code gibt, der davon ausgeht, dass "jeder Prozessor wie ein 80386 aussieht". Architekturen werden unter Berücksichtigung von C entworfen: und zwar nicht nur unter Berücksichtigung von C, sondern auch unter Berücksichtigung allgemeiner Missverständnisse hinsichtlich der C-Portabilität. Sie können eine Maschine mit 9-Bit-Bytes oder was auch immer für allgemeine Zwecke nicht mehr einführen. Programme, die davon ausgehen, dass der Typ
char
ist genau 8 Bit breit wird brechen. Nur einige Programme, die von Portabilitätsexperten geschrieben wurden, werden weiterhin funktionieren: wahrscheinlich nicht genug, um ein komplettes System mit einer Toolchain, einem Kernel, Benutzerbereich und nützlichen Anwendungen mit angemessenem Aufwand zusammenzuführen. Mit anderen Worten, C-Typen sehen aus wie das, was von der Hardware verfügbar ist, da die Hardware so gestaltet wurde, dass sie wie eine andere Hardware aussieht, für die viele nicht portierbare C-Programme geschrieben wurden.Datentypen, die in vielen Maschinensprachen nicht direkt unterstützt werden: Multi-Precision Integer; verknüpfte Liste; Hash-tabelle; Zeichenkette.
Kontrollstrukturen, die in den meisten Maschinensprachen nicht direkt unterstützt werden: erstklassige Fortsetzung; Coroutine / Faden; Generator; Ausnahmebehandlung.
All dies erfordert einen beträchtlichen Laufzeit-Support-Code, der mit zahlreichen Allzweckanweisungen und elementareren Datentypen erstellt wurde.
C verfügt über einige Standarddatentypen, die von einigen Computern nicht unterstützt werden. Seit C99 hat C komplexe Zahlen. Sie bestehen aus zwei Gleitkommawerten und funktionieren mit Bibliotheksroutinen. Einige Maschinen haben überhaupt keine Gleitkommaeinheit.
In Bezug auf einige Datentypen ist dies nicht klar. Wenn eine Maschine die Adressierung des Speichers unter Verwendung eines Registers als Basisadresse und eines anderen als skalierte Verschiebung unterstützt, bedeutet dies, dass Arrays ein direkt unterstützter Datentyp sind?
Apropos Gleitkomma gibt es auch eine Standardisierung: IEEE 754-Gleitkomma. Warum Ihr C-Compiler ein hat,
double
das mit dem vom Prozessor unterstützten Gleitkommaformat übereinstimmt, liegt nicht nur daran, dass beide übereinstimmen, sondern auch daran, dass es für diese Darstellung einen unabhängigen Standard gibt.quelle
Dinge wie
Listen Wird in fast allen funktionalen Sprachen verwendet.
Ausnahmen .
Assoziative Arrays (Maps) - enthalten zB PHP und Perl.
Müllabfuhr .
Datentypen / Kontrollstrukturen, die in vielen Sprachen enthalten sind, jedoch nicht direkt von der CPU unterstützt werden.
quelle
Direkt unterstützt sollte als effiziente Zuordnung zum Befehlssatz des Prozessors verstanden werden.
Die direkte Unterstützung für Ganzzahltypen ist die Regel, mit Ausnahme der langen (möglicherweise erweiterte arithmetische Routinen) und kurzen Größen (möglicherweise Maskierung erforderlich).
Für die direkte Unterstützung von Gleitkommatypen muss eine FPU verfügbar sein.
Die direkte Unterstützung von Bitfeldern ist außergewöhnlich.
Strukturen und Arrays erfordern eine Adressberechnung, die teilweise direkt unterstützt wird.
Zeiger werden immer direkt über die indirekte Adressierung unterstützt.
goto / if / while / for / do werden direkt von bedingungslosen / bedingten Verzweigungen unterstützt.
Schalter kann direkt unterstützt werden, wenn eine Sprungtabelle angewendet wird.
Funktionsaufrufe werden über die Stack-Features direkt unterstützt.
quelle