Wie werden C-Datentypen „direkt von den meisten Computern unterstützt“?

114

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?

gwg
quelle
1
Heutzutage unterstützt die C-Sprache komplexe Arithmetik, ursprünglich jedoch nicht, da Computer komplexe Zahlen nicht direkt als Datentypen unterstützen.
Jonathan Leffler
12
Eigentlich war es historisch umgekehrt: C wurde aus den damals verfügbaren Hardware-Operationen und -Typen entwickelt.
Basile Starynkevitch
2
Die meisten Computer haben keine direkte Hardware-Unterstützung für Dezimal-Floats
PlasmaHH
3
@MSalters: Ich habe versucht, eine Richtung für die Frage anzugeben: "Gibt es ein Beispiel für einen Datentyp oder eine Kontrollstruktur, die nicht direkt von einem Computer unterstützt wird?" was ich nicht als auf K & R
PlasmaHH
11
Wie ist dies nicht ein Duplikat mehr als 6 Jahre nach dem Start von Stack Overflow?
Peter Mortensen

Antworten:

143

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:

float x = 1.0f, y = 2.0f;
return x + y;

Es wird in so etwas übersetzt:

unsigned x = 0x3f800000, y = 0x40000000;
return _float_add(x, y);

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 longim 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änkt longjmp(), 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.

Dietrich Epp
quelle
2
im Grunde nur Zeiger ... eher im Grunde nur rohe Speicherblöcke. Auch wenn das kein Problem ist und die Antwort trotzdem gut ist.
Deduplikator
2
Sie könnten argumentieren, dass nullterminierte Zeichenfolgen "Hardware-Unterstützung" haben, da der Zeichenfolgenabschluss für die Operation "Sprung wenn Null" der meisten Prozessoren geeignet ist und daher etwas schneller als andere mögliche Implementierungen von Zeichenfolgen ist.
Peteris
1
Ich habe meine eigene Antwort gepostet, um zu erläutern, wie C so gestaltet ist, dass es einfach auf asm abgebildet wird.
Peter Cordes
1
Bitte verwenden Sie nicht die Kollokation "Arrays sind im Grunde nur Zeiger", sie kann einen Anfänger wie OP ernsthaft irreführen. Etwas in der Art von "Arrays werden direkt mithilfe von Zeigern auf Hardwareebene implementiert" wäre IMO besser.
Das paramagnetische Croissant
1
@TheParamagneticCroissant: Ich denke in diesem Zusammenhang ist es angemessen ... Klarheit geht zu Lasten der Präzision.
Dietrich Epp
37

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

John Castleman
quelle
14

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:

int myInt = 10;

würde in der Montage so etwas übersetzen:

myInt dw 1
mov myInt,10

Vergleichen Sie dies mit etwas wie C ++:

MyClass myClass;
myClass.set_myInt(10);

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:

#include <stdio.h>

void main()
{
    int myInt=10;

    printf("%d\n", myInt);
}

Ich habe dies bis zur Assemblierung mit gcc kompiliert. Ich habe die folgende Befehlszeile verwendet, um es zu kompilieren:

gcc -S -O2 test.c

Hier ist die resultierende Assemblersprache:

    .file   "test.c"
    .section    .rodata.str1.1,"aMS",@progbits,1
.LC0:
    .string "%d\n"
    .section    .text.unlikely,"ax",@progbits
.LCOLDB1:
    .section    .text.startup,"ax",@progbits
.LHOTB1:
    .p2align 4,,15
    .globl  main
    .type   main, @function
main:
.LFB24:
    .cfi_startproc
    movl    $10, %edx
    movl    $.LC0, %esi
    movl    $1, %edi
    xorl    %eax, %eax
    jmp __printf_chk
    .cfi_endproc
.LFE24:
    .size   main, .-main
    .section    .text.unlikely
.LCOLDE1:
    .section    .text.startup
.LHOTE1:
    .ident  "GCC: (Ubuntu 4.9.1-16ubuntu6) 4.9.1"
    .section    .note.GNU-stack,"",@progbits

Ich erstelle dann eine Datei namens "test.cpp", die eine Klasse definiert und dasselbe wie "test.c" ausgibt:

#include <iostream>
using namespace std;

class MyClass {
    int myVar;
public:
    void set_myVar(int);
    int get_myVar(void);
};

void MyClass::set_myVar(int val)
{
    myVar = val;
}

int MyClass::get_myVar(void)
{
    return myVar;
}

int main()
{
    MyClass myClass;
    myClass.set_myVar(10);

    cout << myClass.get_myVar() << endl;

    return 0;
}

Ich habe es auf die gleiche Weise mit diesem Befehl kompiliert:

g++ -O2 -S test.cpp

Hier ist die resultierende Assembly-Datei:

    .file   "test.cpp"
    .section    .text.unlikely,"ax",@progbits
    .align 2
.LCOLDB0:
    .text
.LHOTB0:
    .align 2
    .p2align 4,,15
    .globl  _ZN7MyClass9set_myVarEi
    .type   _ZN7MyClass9set_myVarEi, @function
_ZN7MyClass9set_myVarEi:
.LFB1047:
    .cfi_startproc
    movl    %esi, (%rdi)
    ret
    .cfi_endproc
.LFE1047:
    .size   _ZN7MyClass9set_myVarEi, .-_ZN7MyClass9set_myVarEi
    .section    .text.unlikely
.LCOLDE0:
    .text
.LHOTE0:
    .section    .text.unlikely
    .align 2
.LCOLDB1:
    .text
.LHOTB1:
    .align 2
    .p2align 4,,15
    .globl  _ZN7MyClass9get_myVarEv
    .type   _ZN7MyClass9get_myVarEv, @function
_ZN7MyClass9get_myVarEv:
.LFB1048:
    .cfi_startproc
    movl    (%rdi), %eax
    ret
    .cfi_endproc
.LFE1048:
    .size   _ZN7MyClass9get_myVarEv, .-_ZN7MyClass9get_myVarEv
    .section    .text.unlikely
.LCOLDE1:
    .text
.LHOTE1:
    .section    .text.unlikely
.LCOLDB2:
    .section    .text.startup,"ax",@progbits
.LHOTB2:
    .p2align 4,,15
    .globl  main
    .type   main, @function
main:
.LFB1049:
    .cfi_startproc
    subq    $8, %rsp
    .cfi_def_cfa_offset 16
    movl    $10, %esi
    movl    $_ZSt4cout, %edi
    call    _ZNSolsEi
    movq    %rax, %rdi
    call    _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
    xorl    %eax, %eax
    addq    $8, %rsp
    .cfi_def_cfa_offset 8
    ret
    .cfi_endproc
.LFE1049:
    .size   main, .-main
    .section    .text.unlikely
.LCOLDE2:
    .section    .text.startup
.LHOTE2:
    .section    .text.unlikely
.LCOLDB3:
    .section    .text.startup
.LHOTB3:
    .p2align 4,,15
    .type   _GLOBAL__sub_I__ZN7MyClass9set_myVarEi, @function
_GLOBAL__sub_I__ZN7MyClass9set_myVarEi:
.LFB1056:
    .cfi_startproc
    subq    $8, %rsp
    .cfi_def_cfa_offset 16
    movl    $_ZStL8__ioinit, %edi
    call    _ZNSt8ios_base4InitC1Ev
    movl    $__dso_handle, %edx
    movl    $_ZStL8__ioinit, %esi
    movl    $_ZNSt8ios_base4InitD1Ev, %edi
    addq    $8, %rsp
    .cfi_def_cfa_offset 8
    jmp __cxa_atexit
    .cfi_endproc
.LFE1056:
    .size   _GLOBAL__sub_I__ZN7MyClass9set_myVarEi, .-_GLOBAL__sub_I__ZN7MyClass9set_myVarEi
    .section    .text.unlikely
.LCOLDE3:
    .section    .text.startup
.LHOTE3:
    .section    .init_array,"aw"
    .align 8
    .quad   _GLOBAL__sub_I__ZN7MyClass9set_myVarEi
    .local  _ZStL8__ioinit
    .comm   _ZStL8__ioinit,1,1
    .hidden __dso_handle
    .ident  "GCC: (Ubuntu 4.9.1-16ubuntu6) 4.9.1"
    .section    .note.GNU-stack,"",@progbits

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.

Icemanind
quelle
14
Dieser "C ++ - Code" ist einfach nicht C ++. Und echter Code wie 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 Cs qsortreal, aber C ++ std::sorthat auch nach einer grundlegenden Optimierung keine Abstraktionsstrafe.
MSalters
1
Mit IDA Pro können Sie leicht erkennen, dass die meisten C ++ - Konstrukte auf die gleiche Weise kompiliert werden wie manuell in C, Konstruktoren und Dtoren werden für triviale Objekte eingefügt, und dann wird die zukünftige Optimierung angewendet
Paulm
7

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:

C kombiniert die Flexibilität und Leistungsfähigkeit der Assemblersprache mit der Benutzerfreundlichkeit der Assemblersprache.

( 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 generierencall 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.

Peter Cordes
quelle
"K & R bedeutet, dass die meisten C-Ausdrücke (technische Bedeutung) einer oder mehreren Assembly-Anweisungen zugeordnet sind, nicht einem Funktionsaufruf einer Support-Bibliothek." Dies ist eine sehr intuitive Erklärung. Vielen Dank.
GWG
1
Ich bin gerade auf den Begriff "von Neumann-Sprache" gestoßen ( en.wikipedia.org/wiki/Von_Neumann_programming_languages ). Es ist genau das, was C ist.
Peter Cordes
1
Dies ist genau der Grund, warum ich C verwende. Was mich jedoch beim Erlernen von C überrascht hat, ist, dass der Versuch, für eine breite Palette von Hardware effizient zu sein, auf den meisten modernen Hardwarekomponenten manchmal unfähig und ineffizient ist. Ich meine zum Beispiel keinen nützlichen und zuverlässigen Weg, um einen ganzzahligen Überlauf in c zu erkennen, und das Hinzufügen mehrerer Wörter unter Verwendung des Übertragsflags .
Z Boson
6

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"

terary
quelle
5

Hüten Sie sich vor irreführenden Vergleichen

  1. Die Anweisung basiert auf dem Begriff einer "Laufzeitbibliothek". , die zumindest für Mainstream-Hochsprachen seitdem weitgehend aus der Mode gekommen ist. (Dies ist immer noch für die kleinsten eingebetteten Systeme relevant.) Die Laufzeit ist die minimale Unterstützung, die ein Programm in dieser Sprache ausführen muss, wenn Sie nur in die Sprache integrierte Konstrukte verwenden (im Gegensatz zum expliziten Aufrufen einer von einer Bibliothek bereitgestellten Funktion). .
  2. Im Gegensatz dazu neigen moderne Sprachen dazu, nicht zwischen der Laufzeit- und der Standardbibliothek zu unterscheiden , wobei letztere häufig recht umfangreich ist.
  3. Zum Zeitpunkt des K & R Buchs, C haben nicht einmal eine Standard - Bibliothek . Vielmehr unterschieden sich die verfügbaren C-Bibliotheken zwischen den verschiedenen Unix-Varianten erheblich.
  4. Um die Aussage zu verstehen, sollten Sie nicht mit Sprachen mit einer Standardbibliothek (wie Lua und Python, die in anderen Antworten erwähnt wurden) vergleichen, sondern mit Sprachen mit stärker integrierten Konstrukten (wie LISP aus alter Zeit und FORTRAN aus alter Zeit, die in anderen Antworten erwähnt werden) Antworten). Andere Beispiele wären BASIC (interaktiv wie LISP) oder PASCAL (kompiliert wie FORTRAN), die beide (unter anderem) Eingabe- / Ausgabefunktionen haben, die direkt in die Sprache selbst integriert sind.
  5. Im Gegensatz dazu gibt es keine Standardmethode, um die Berechnungsergebnisse aus einem C-Programm herauszuholen, das nur die Laufzeit und keine Bibliothek verwendet.
Lutz Prechelt
quelle
Andererseits werden die meisten modernen Sprachen in dedizierten Laufzeitumgebungen ausgeführt, die Funktionen wie die Speicherbereinigung bereitstellen.
Nate CK
5

Gibt es ein Beispiel für einen Datentyp oder eine Kontrollstruktur, die nicht direkt von einem Computer unterstützt wird?

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:

  • Manipulation von Textzeichenfolgen beliebiger Länge - Verkettung, Teilzeichenfolge, Zuweisen einer neuen Zeichenfolge zu einer Variablen, die mit einer anderen Zeichenfolge initialisiert wurde usw. ('s = "Hallo Welt!"; s = (s + s) [2: -2] 'in Python)
  • setzt
  • Objekte mit verschachtelten virtuellen Destruktoren, wie in C ++ und jeder anderen objektorientierten Programmiersprache
  • 2D-Matrixmultiplikation und -division; Lösen linearer Systeme ("C = B / A; x = A \ b" in MATLAB und vielen Array-Programmiersprachen)
  • Reguläre Ausdrücke
  • Arrays mit variabler Länge - insbesondere das Anhängen eines Elements an das Ende des Arrays, was (manchmal) die Zuweisung von mehr Speicher erfordert.
  • Lesen des Werts von Variablen, deren Typ zur Laufzeit geändert wird - manchmal ist es ein Float, manchmal ist es eine Zeichenfolge
  • assoziative Arrays (oft als "Karten" oder "Wörterbücher" bezeichnet)
  • Listen
  • Verhältnisse ("(+ 1/3 2/7)" ergeben "13/21" in Lisp )
  • Arithmetik mit beliebiger Genauigkeit (oft als "Bignum" bezeichnet)
  • Konvertieren von Daten in eine druckbare Darstellung (die ".tostring" -Methode in JavaScript)
  • Sättigung von Festkommazahlen (häufig in eingebetteten C-Programmen verwendet)
  • Auswerten einer zur Laufzeit eingegebenen Zeichenfolge als Ausdruck ("eval ()" in vielen Programmiersprachen).

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:

  • Verschlüsse
  • Fortsetzung
  • Ausnahmen
  • faule Bewertung

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.

David Cary
quelle
Wenn Ihre Implementierung von Lisp (+ 1/3 2/7) als 3/21 bewertet, denke ich, dass Sie eine besonders kreative Implementierung haben müssen ...
RobertB
4

Was sind die eingebauten Datentypen C? Sie sind Dinge wie int, 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.

hhafez
quelle
2
x86 kann mit einigen Arrays arbeiten, aber nicht mit allen. Bei großen oder ungewöhnlichen Elementgrößen muss eine Ganzzahlarithmetik ausgeführt werden, um einen Array-Index in einen Zeigerversatz umzuwandeln. Auf anderen Plattformen ist dies immer erforderlich. Und die Idee, dass die CPU C ++ - Klassen nicht versteht, ist lächerlich. Das sind nur Zeiger-Offsets wie C-Strukturen. Dafür brauchen Sie keine Laufzeit.
MSalters
@MSalters ja, aber die eigentlichen Methoden der Standardbibliotheksklassen wie iostreams usw. sind Bibliotheksfunktionen und werden nicht direkt vom Compiler unterstützt. Die höheren Sprachen, mit denen sie es wahrscheinlich verglichen haben, waren jedoch nicht C ++, sondern zeitgenössische Sprachen wie FORTRAN und PL / I.
Random832
1
C ++ - Klassen mit virtuellen Elementfunktionen übersetzen viel mehr als nur einen Offset in eine Struktur.
Peter Cordes
4

Das hängt vom Computer ab. Auf dem PDP-11, auf dem C erfunden wurde, longwurde 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 ( floatoder double) 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 *oder void *) 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, und csv(und cretauch 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.

Random832
quelle
3

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 intoder longentsprechend angepasst werden. Programme, die intgenau 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 Typcharist 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.

Gibt es ein Beispiel für einen Datentyp oder eine Kontrollstruktur, die nicht direkt von einem Computer unterstützt wird?

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, doubledas 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.

Kaz
quelle
2

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.

MTilsted
quelle
2

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.

Yves Daoust
quelle