Comment les types de données C sont-ils «pris en charge directement par la plupart des ordinateurs»?

114

Je lis «Le langage de programmation C» de K&R et je suis tombé sur cette déclaration [Introduction, p. 3]:

Étant donné que les types de données et les structures de contrôle fournis par C sont pris en charge directement par la plupart des ordinateurs , la bibliothèque d'exécution requise pour implémenter des programmes autonomes est minuscule.

Que signifie la déclaration en gras? Existe-t-il un exemple de type de données ou de structure de contrôle qui n'est pas pris en charge directement par un ordinateur?

gwg
la source
1
De nos jours, le langage C prend en charge l'arithmétique complexe, mais à l'origine, il ne l'a pas fait car les ordinateurs ne prennent pas directement en charge les nombres complexes en tant que types de données.
Jonathan Leffler
12
En fait, c'était historiquement l'inverse: C a été conçu à partir des opérations et des types de matériel disponibles à cette époque.
Basile Starynkevitch
2
La plupart des ordinateurs n'ont pas de support matériel direct pour les flottants décimaux
PlasmaHH
3
@MSalters: J'essayais d'indiquer une direction pour la question "Y a-t-il un exemple de type de données ou de structure de contrôle qui n'est pas pris en charge directement par un ordinateur?" que je n'ai pas interprété comme étant limité à K&R
PlasmaHH
11
Comment n'est-ce pas un doublon plus de 6 ans après le lancement de Stack Overflow?
Peter Mortensen

Réponses:

143

Oui, certains types de données ne sont pas directement pris en charge.

Sur de nombreux systèmes embarqués, il n'y a pas d'unité matérielle à virgule flottante. Donc, lorsque vous écrivez un code comme celui-ci:

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

Cela se traduit par quelque chose comme ceci:

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

Ensuite, le compilateur ou la bibliothèque standard doit fournir une implémentation de _float_add(), qui occupe de la mémoire sur votre système embarqué. Si vous comptez des octets sur un système très petit, cela peut s'additionner.

Un autre exemple courant est celui des entiers 64 bits ( long longdans la norme C depuis 1999), qui ne sont pas directement pris en charge par les systèmes 32 bits. Les anciens systèmes SPARC ne supportaient pas la multiplication d'entiers, donc la multiplication devait être fournie par le runtime. Il y a d'autres exemples.

Autres langues

En comparaison, d'autres langages ont des primitives plus compliquées.

Par exemple, un symbole Lisp nécessite beaucoup de support à l'exécution, tout comme les tables en Lua, les chaînes en Python, les tableaux en Fortran, et cetera. Les types équivalents en C ne font généralement pas partie de la bibliothèque standard du tout (pas de symboles ou de tables standard) ou ils sont beaucoup plus simples et ne nécessitent pas beaucoup de support d'exécution (les tableaux en C ne sont fondamentalement que des pointeurs, les chaînes à terminaison nulle sont presque aussi simple).

Structures de contrôle

Une structure de contrôle notable absente de C est la gestion des exceptions. La sortie non locale est limitée à setjmp()et longjmp(), qui ne font que sauvegarder et restaurer certaines parties de l'état du processeur. Par comparaison, le runtime C ++ doit parcourir la pile et appeler des destructeurs et des gestionnaires d'exceptions.

Dietrich Epp
la source
2
essentiellement des pointeurs ... plutôt, des morceaux de mémoire bruts. Même si cela est difficile, et la réponse est bonne de toute façon.
Deduplicator
2
Vous pourriez argumenter que les chaînes terminées par null ont un "support matériel" car le terminateur de chaîne correspond à l'opération "sauter si zéro" de la plupart des processeurs et est donc légèrement plus rapide que d'autres implémentations possibles de chaînes.
Peteris
1
J'ai publié ma propre réponse pour expliquer comment C est conçu pour être simplement associé à asm.
Peter Cordes
1
Veuillez ne pas utiliser la collocation "les tableaux ne sont en fait que des pointeurs", cela peut sérieusement, gravement induire en erreur un débutant comme OP. Quelque chose comme "les tableaux sont directement implémentés en utilisant des pointeurs au niveau matériel" serait mieux IMO.
Le Croissant Paramagnétique
1
@TheParamagneticCroissant: Je pense que dans ce contexte c'est approprié ... la clarté vient au détriment de la précision.
Dietrich Epp
37

En fait, je parie que le contenu de cette introduction n'a pas beaucoup changé depuis 1978 lorsque Kernighan et Ritchie les ont écrits pour la première fois dans la première édition du livre, et ils se réfèrent à l'histoire et à l'évolution de C à cette époque plus que moderne. implémentations.

Les ordinateurs ne sont fondamentalement que des banques de mémoire et des processeurs centraux, et chaque processeur fonctionne à l'aide d'un code machine; une partie de la conception de chaque processeur est une architecture de jeu d'instructions, appelée langage d'assemblage , qui mappe un à un d'un ensemble de mnémoniques lisibles par l'homme au code machine, qui est tous des nombres.

Les auteurs du langage C - et des langages B et BCPL qui l'ont immédiatement précédé - avaient l'intention de définir des constructions dans le langage qui étaient aussi efficacement compilées dans Assembly que possible ... en fait, ils ont été contraints par des limitations dans la cible Matériel. Comme d'autres réponses l'ont souligné, cela impliquait des branches (GOTO et autres contrôles de flux en C), des déplacements (affectation), des opérations logiques (& | ^), de l'arithmétique de base (ajouter, soustraire, incrémenter, décrémenter) et l'adressage mémoire (pointeurs ). Un bon exemple est les opérateurs pré / post-incrémentation et décrémentation en C, qui auraient été ajoutés au langage B par Ken Thompson spécifiquement parce qu'ils étaient capables de se traduire directement en un seul opcode une fois compilés.

C'est ce que les auteurs voulaient dire lorsqu'ils disaient «pris en charge directement par la plupart des ordinateurs». Ils ne signifiaient pas que d'autres langages contenaient des types et des structures qui n'étaient pas pris en charge directement - ils signifiaient que, par conception, les constructions C traduites le plus directement (parfois littéralement directement) en Assembly.

Cette relation étroite avec l'assembly sous-jacent, tout en fournissant tous les éléments nécessaires à la programmation structurée, est ce qui a conduit à l'adoption précoce de C, et ce qui en fait un langage populaire aujourd'hui dans des environnements où l'efficacité du code compilé est toujours essentielle.

Pour un article intéressant sur l'histoire du langage, voir Le développement du langage C - Dennis Ritchie

John Castleman
la source
14

La réponse courte est que la plupart des constructions de langage prises en charge par C sont également prises en charge par le microprocesseur de l'ordinateur cible.Par conséquent, le code C compilé se traduit très bien et efficacement dans le langage d'assemblage du microprocesseur, ce qui entraîne un code plus petit et une empreinte plus petite.

La réponse plus longue nécessite un peu de connaissance du langage assembleur. En C, une déclaration comme celle-ci:

int myInt = 10;

se traduirait par quelque chose comme ça dans l'assemblage:

myInt dw 1
mov myInt,10

Comparez cela à quelque chose comme C ++:

MyClass myClass;
myClass.set_myInt(10);

Le code de langage d'assemblage résultant (selon la taille de MyClass ()), pourrait ajouter jusqu'à des centaines de lignes de langage d'assemblage.

Sans réellement créer de programmes en langage assembleur, le C pur est probablement le code le plus «maigre» et le plus «serré» dans lequel vous pouvez créer un programme.

ÉDITER

Compte tenu des commentaires sur ma réponse, j'ai décidé de faire un test, juste pour ma propre raison. J'ai créé un programme appelé "test.c", qui ressemblait à ceci:

#include <stdio.h>

void main()
{
    int myInt=10;

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

J'ai compilé cela jusqu'à l'assemblage en utilisant gcc. J'ai utilisé la ligne de commande suivante pour le compiler:

gcc -S -O2 test.c

Voici le langage d'assemblage résultant:

    .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

Je crée ensuite un fichier appelé "test.cpp" qui définit une classe et produit la même chose que "test.c":

#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;
}

Je l'ai compilé de la même manière, en utilisant cette commande:

g++ -O2 -S test.cpp

Voici le fichier d'assemblage résultant:

    .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

Comme vous pouvez le voir clairement, le fichier d'assemblage résultant est beaucoup plus volumineux sur le fichier C ++ que sur le fichier C. Même si vous supprimez tous les autres éléments et comparez simplement le C "principal" au "principal" C ++, il y a beaucoup de choses supplémentaires.

Icemanind
la source
14
Ce «code C ++» n'est tout simplement pas C ++. Et le vrai code tel qu'en MyClass myClass { 10 }C ++ est très susceptible de se compiler exactement dans le même assembly. Les compilateurs C ++ modernes ont éliminé la pénalité d'abstraction. Et en conséquence, ils peuvent souvent battre les compilateurs C. Par exemple, la pénalité d'abstraction en C qsortest réelle, mais celle en C ++ std::sortn'a aucune pénalité d'abstraction même après une optimisation de base.
MSalters
1
Vous pouvez facilement voir en utilisant IDA Pro que la plupart des constructions C ++ se compilent de la même manière que le faire manuellement en C, les constructeurs et les dtors sont intégrés pour les objets triviaux, puis l'optimisation future est appliquée
paulm
7

K&R signifie que la plupart des expressions C (signification technique) correspondent à une ou plusieurs instructions d'assemblage, et non à un appel de fonction à une bibliothèque de support. Les exceptions habituelles sont la division entière sur les architectures sans instruction div matérielle, ou la virgule flottante sur les machines sans FPU.

Il y a une citation:

C combine la flexibilité et la puissance du langage d'assemblage avec la convivialité du langage d'assemblage.

( trouvé ici . Je pensais me souvenir d'une variante différente, comme "la vitesse du langage d'assemblage avec la commodité et l'expressivité du langage d'assemblage".)

long int a généralement la même largeur que les registres de la machine native.

Certains langages de niveau supérieur définissent la largeur exacte de leurs types de données et les implémentations sur toutes les machines doivent fonctionner de la même manière. Pas C, cependant.

Si vous voulez travailler avec des ints 128 bits sur x86-64, ou dans le cas général BigInteger de taille arbitraire, vous avez besoin d'une bibliothèque de fonctions pour cela. Tous les processeurs utilisent maintenant le complément 2s comme représentation binaire d'entiers négatifs, mais même ce n'était pas le cas à l'époque de la conception de C. (C'est pourquoi certaines choses qui donneraient des résultats différents sur des machines non complémentaires de 2s ne sont techniquement pas définies dans les normes C.)

Les pointeurs C vers des données ou des fonctions fonctionnent de la même manière que les adresses d'assemblage.

Si vous voulez des références ref-countées, vous devez le faire vous-même. Si vous voulez des fonctions membres virtuelles C ++ qui appellent une fonction différente selon le type d'objet vers lequel pointe votre pointeur, le compilateur C ++ doit générer beaucoup plus qu'une simple callinstruction avec une adresse fixe.

Les chaînes ne sont que des tableaux

En dehors des fonctions de bibliothèque, les seules opérations de chaîne fournies sont la lecture / écriture d'un caractère. Pas de concat, pas de sous-chaîne, pas de recherche. (Les chaînes sont stockées sous forme de '\0'tableaux à terminaison nulle ( ) d'entiers 8 bits, pas de pointeur + longueur, donc pour obtenir une sous-chaîne, vous devez écrire un nul dans la chaîne d'origine.)

Les processeurs ont parfois des instructions conçues pour être utilisées par une fonction de recherche de chaînes, mais traitent toujours généralement un octet par instruction exécutée, dans une boucle. (ou avec le préfixe de rep x86. Peut-être que si C était conçu sur x86, la recherche ou la comparaison de chaînes serait une opération native, plutôt qu'un appel de fonction de bibliothèque.)

Beaucoup d'autres réponses donnent des exemples de choses qui ne sont pas prises en charge nativement, comme la gestion des exceptions, les tables de hachage, les listes. La philosophie de conception de K&R est la raison pour laquelle C n'en a pas nativement.

Peter Cordes
la source
"K&R signifie que la plupart des expressions C (signification technique) correspondent à une ou quelques instructions d'assemblage, pas à un appel de fonction à une bibliothèque de support." C'est une explication très intuitive. Merci.
gwg le
1
Je viens de tomber sur le terme "langage von Neumann" ( en.wikipedia.org/wiki/Von_Neumann_programming_languages ). C'est exactement ce qu'est C.
Peter Cordes
1
C'est exactement pourquoi j'utilise C. Mais ce qui m'a surpris en apprenant C, c'est qu'en essayant d'être efficace pour une large gamme de matériel, il est parfois inepte et inefficace sur la plupart des matériels modernes. Je veux dire par exemple pas de moyen utile et fiable de détecter le débordement d'entier en c et l' addition de plusieurs mots à l'aide du drapeau de report .
Z boson
6

Le langage d'assemblage d'un processus traite généralement des sauts (aller à), des instructions, des instructions de déplacement, des arthrites binaires (XOR, NAND, AND OR, etc.), des champs de mémoire (ou d'adresse). Classe la mémoire en deux types, instruction et données. C'est à peu près tout ce qu'est un langage d'assemblage (je suis sûr que les programmeurs d'assemblage diront qu'il y a plus que cela, mais cela se résume à cela en général). C ressemble beaucoup à cette simplicité.

C est d'assembler ce que l'algèbre est à l'arithmétique.

C encapsule les bases de l'assemblage (le langage du processeur). Est probablement une déclaration plus vraie que "Parce que les types de données et les structures de contrôle fournis par C sont pris en charge directement par la plupart des ordinateurs"

terary
la source
5

Méfiez-vous des comparaisons trompeuses

  1. La déclaration s'appuie sur la notion de «bibliothèque d'exécution» , qui est pour la plupart passée de mode depuis, du moins pour les langages de haut niveau traditionnels. (Il est toujours pertinent pour les plus petits systèmes embarqués.) Le run-time est le support minimal qu'un programme dans ce langage nécessite pour s'exécuter lorsque vous n'utilisez que des constructions intégrées dans le langage (par opposition à l'appel explicite d'une fonction fournie par une bibliothèque) .
  2. En revanche, les langues modernes ont tendance à ne pas faire de distinction entre la bibliothèque d'exécution et la bibliothèque standard , cette dernière étant souvent assez étendue.
  3. Au moment du livre K&R, C n'avait même pas de bibliothèque standard . Au contraire, les bibliothèques C disponibles différaient un peu entre les différentes versions d'Unix.
  4. Pour comprendre la déclaration, vous ne devez pas comparer aux langages avec une bibliothèque standard (comme Lua et Python mentionnés dans d'autres réponses), mais à des langages avec des constructions plus intégrées (comme l'ancien LISP et l'ancien FORTRAN mentionné dans d'autres réponses). D'autres exemples seraient BASIC (interactif, comme LISP) ou PASCAL (compilé, comme FORTRAN) qui ont tous deux (entre autres) des fonctionnalités d'entrée / sortie intégrées directement dans le langage lui-même.
  5. En revanche, il n'existe aucun moyen standard d'obtenir les résultats du calcul à partir d'un programme C qui n'utilise que le moteur d'exécution, pas n'importe quelle bibliothèque.
Lutz Prechelt
la source
D'un autre côté, la plupart des langages modernes s'exécutent dans des environnements d'exécution dédiés qui fournissent des fonctionnalités telles que le ramasse-miettes.
Nate CK
5

Existe-t-il un exemple de type de données ou de structure de contrôle qui n'est pas pris en charge directement par un ordinateur?

Tous les types de données fondamentaux et leurs opérations en langage C peuvent être implémentés par une ou quelques instructions en langage machine sans boucle - ils sont directement pris en charge par le (pratiquement tous) CPU.

Plusieurs types de données populaires et leurs opérations nécessitent des dizaines d'instructions en langage machine, ou nécessitent l'itération d'une boucle d'exécution, ou des deux.

De nombreux langages ont une syntaxe abrégée spéciale pour ces types et leurs opérations - l'utilisation de ces types de données en C nécessite généralement de taper beaucoup plus de code.

Ces types de données et opérations comprennent:

  • manipulation de chaîne de texte de longueur arbitraire - concaténation, sous-chaîne, attribution d'une nouvelle chaîne à une variable initialisée avec une autre chaîne, etc. ('s = "Hello World!"; s = (s + s) [2: -2] 'en Python)
  • ensembles
  • objets avec des destructeurs virtuels imbriqués, comme dans C ++ et tout autre langage de programmation orienté objet
  • Multiplication et division de matrice 2D; résolution de systèmes linéaires ("C = B / A; x = A \ b" dans MATLAB et de nombreux langages de programmation de tableaux)
  • expressions régulières
  • tableaux de longueur variable - en particulier, ajouter un élément à la fin du tableau, ce qui nécessite (parfois) d'allouer plus de mémoire.
  • lire la valeur des variables qui changent de type à l'exécution - parfois c'est un flottant, d'autres fois c'est une chaîne
  • tableaux associatifs (souvent appelés «cartes» ou «dictionnaires»)
  • listes
  • ratios ("(+ 1/3 2/7)" donne "13/21" en Lisp )
  • arithmétique à précision arbitraire (souvent appelée "bignums")
  • conversion de données en une représentation imprimable (méthode ".tostring" en JavaScript)
  • saturation des nombres à virgule fixe (souvent utilisés dans les programmes C embarqués)
  • évaluer une chaîne saisie au moment de l'exécution comme s'il s'agissait d'une expression ("eval ()" dans de nombreux langages de programmation).

Toutes ces opérations nécessitent des dizaines d'instructions en langage machine ou nécessitent l'itération d'une boucle d'exécution sur presque tous les processeurs.

Certaines structures de contrôle populaires qui nécessitent également des dizaines d'instructions en langage machine ou des boucles incluent:

  • fermetures
  • continuations
  • des exceptions
  • évaluation paresseuse

Qu'il soit écrit en C ou dans un autre langage, lorsqu'un programme manipule de tels types de données, la CPU doit finalement exécuter toutes les instructions nécessaires pour manipuler ces types de données. Ces instructions sont souvent contenues dans une "bibliothèque". Chaque langage de programmation, même C, a une "bibliothèque d'exécution" pour chaque plate-forme qui est incluse par défaut dans chaque exécutable.

La plupart des gens qui écrivent des compilateurs mettent les instructions pour manipuler tous les types de données qui sont «intégrés dans le langage» dans leur bibliothèque d'exécution. Étant donné que C n'a aucun des types de données, des opérations et des structures de contrôle ci-dessus intégrés dans le langage, aucun d'entre eux n'est inclus dans la bibliothèque d'exécution C - ce qui rend la bibliothèque d'exécution C plus petite que l'exécution- bibliothèque temporelle d'autres langages de programmation qui ont plus des éléments ci-dessus intégrés au langage.

Lorsqu'un programmeur veut qu'un programme - en C ou dans tout autre langage de son choix - manipule d'autres types de données qui ne sont pas "intégrés au langage", ce programmeur dit généralement au compilateur d'inclure des bibliothèques supplémentaires avec ce programme, ou parfois (pour "éviter les dépendances") écrit encore une autre implémentation de ces opérations directement dans le programme.

David Cary
la source
Si votre implémentation de Lisp évalue (+ 1/3 2/7) à 3/21, je pense que vous devez avoir une implémentation particulièrement créative ...
RobertB
4

Quels sont les types de données intégrés dans C? Ce sont des choses comme int, char, * int, float, tableaux , etc ... Ces types de données sont compris par la CPU. Le CPU sait comment travailler avec des tableaux, comment déréférencer des pointeurs et comment effectuer de l'arithmétique sur des pointeurs, des entiers et des nombres à virgule flottante.

Mais lorsque vous passez à des langages de programmation de niveau supérieur, vous avez intégré des types de données abstraits et des constructions plus complexes. Par exemple, regardez le vaste éventail de classes intégrées dans le langage de programmation C ++. Le processeur ne comprend pas les classes, les objets ou les types de données abstraits, de sorte que l'exécution C ++ comble le fossé entre le processeur et le langage. Voici des exemples de types de données qui ne sont pas directement pris en charge par la plupart des ordinateurs.

Hhafez
la source
2
x86 sait fonctionner avec certains tableaux, mais pas tous. Pour des tailles d'élément importantes ou inhabituelles, il devra effectuer une arithmétique entière pour convertir un index de tableau en un décalage de pointeur. Et sur d'autres plates-formes, cela est toujours nécessaire. Et l'idée que le CPU ne comprend pas les classes C ++ est risible. Ce ne sont que des décalages de pointeur, comme les structures C. Vous n'avez pas besoin d'un runtime pour cela.
MSalters
@MSalters oui, mais les méthodes réelles des classes de bibliothèque standard comme iostreams, etc. sont des fonctions de bibliothèque plutôt que d'être directement prises en charge par le compilateur. Cependant, les langages de niveau supérieur auxquels ils le comparaient probablement n'étaient pas C ++, mais des langages contemporains tels que FORTRAN et PL / I.
Random832
1
Les classes C ++ avec des fonctions membres virtuelles se traduisent par bien plus qu'un simple décalage en struct.
Peter Cordes
4

Cela dépend de l'ordinateur. Le PDP-11, où C a été inventé, longétait mal pris en charge (il y avait un module complémentaire optionnel que vous pouviez acheter qui prenait en charge certaines, mais pas toutes, les opérations 32 bits). La même chose est vraie à divers degrés sur n'importe quel système 16 bits, y compris l'IBM PC d'origine. Et de même pour les opérations 64 bits sur des machines 32 bits ou dans des programmes 32 bits, bien que le langage C à l'époque du livre K&R n'ait pas du tout d'opérations 64 bits. Et bien sûr, il y a eu de nombreux systèmes au cours des années 80 et 90 [y compris les processeurs 386 et 486], et même certains systèmes embarqués aujourd'hui, qui ne supportaient pas directement l'arithmétique en virgule flottante ( floatou double).

Pour un exemple plus exotique, certaines architectures informatiques ne prennent en charge que les pointeurs «orientés mot» (pointant vers un entier de deux ou quatre octets en mémoire), et les pointeurs d'octets ( char *ou void *) ont dû être implémentés en ajoutant un champ de décalage supplémentaire. Cette question entre dans certains détails sur ces systèmes.

Les fonctions de « bibliothèque d'exécution» auxquelles il fait référence ne sont pas celles que vous verrez dans le manuel, mais des fonctions comme celles-ci, dans la bibliothèque d'exécution d'un compilateur moderne , qui sont utilisées pour implémenter les opérations de type de base qui ne sont pas prises en charge par la machine . La bibliothèque d'exécution à laquelle K&R se référait se trouve sur le site Web de la Unix Heritage Society - vous pouvez voir des fonctions comme ldiv(distincte de la fonction C du même nom, qui n'existait pas à l'époque) qui est utilisée pour implémenter la division de Valeurs 32 bits, que le PDP-11 ne supportait pas même avec l'add-on, et csv(et cretaussi dans csv.c) qui sauvegardent et restaurent les registres sur la pile pour gérer les appels et les retours de fonctions.

Ils faisaient probablement également référence à leur choix de ne pas prendre en charge de nombreux types de données qui ne sont pas directement pris en charge par la machine sous-jacente, contrairement à d'autres langages contemporains tels que FORTRAN, qui avaient une sémantique de tableau qui ne correspondait pas aussi bien au support de pointeur sous-jacent du processeur que Les tableaux de C. Le fait que les tableaux C soient toujours indexés à zéro et toujours de taille connue dans tous les rangs, mais le premier signifie qu'il n'est pas nécessaire de stocker les plages d'index ou les tailles des tableaux, et pas besoin d'avoir des fonctions de bibliothèque d'exécution pour y accéder - le compilateur peut simplement coder en dur l'arithmétique nécessaire du pointeur.

Aléatoire832
la source
3

L'instruction signifie simplement que les données et les structures de contrôle en C sont orientées machine.

Il y a deux aspects à considérer ici. La première est que le langage C a une définition (norme ISO) qui permet une certaine latitude dans la manière dont les types de données sont définis. Cela signifie que les implémentations du langage C sont adaptées à la machine . Les types de données d'un compilateur C correspondent à ce qui est disponible dans la machine ciblée par le compilateur, car le langage a la latitude pour cela. Si une machine a une taille de mot inhabituelle, comme 36 bits, alors le type intou longpeut être rendu conforme à cela. Les programmes qui supposent que intc'est exactement 32 bits seront interrompus.

Deuxièmement, en raison de ces problèmes de portabilité, il y a un deuxième effet. D'une certaine manière, la déclaration dans le K&R est devenue une sorte de prophétie auto-réalisatrice , ou peut-être à l'inverse. C'est-à-dire que les implémenteurs de nouveaux processeurs sont conscients du besoin criant de supporter les compilateurs C, et ils savent qu'il existe beaucoup de code C qui suppose que "chaque processeur ressemble à un 80386". Les architectures sont conçues avec C à l'esprit: et pas seulement C à l'esprit, mais avec les idées fausses courantes sur la portabilité C à l'esprit également. Vous ne pouvez tout simplement plus introduire une machine avec des octets de 9 bits ou quoi que ce soit à des fins générales. Programmes qui supposent que le typecharest exactement 8 bits de large cassera. Seuls certains programmes écrits par des experts en portabilité continueront de fonctionner: probablement pas assez pour rassembler un système complet avec une chaîne d'outils, un noyau, un espace utilisateur et des applications utiles, avec un effort raisonnable. En d'autres termes, les types C ressemblent à ce qui est disponible à partir du matériel parce que le matériel a été conçu pour ressembler à un autre matériel pour lequel de nombreux programmes C non portables ont été écrits.

Existe-t-il un exemple de type de données ou de structure de contrôle qui n'est pas pris en charge directement par un ordinateur?

Les types de données ne sont pas directement pris en charge dans de nombreux langages machine: entier multi-précision; liste chaînée; table de hachage; chaîne de caractères.

Structures de contrôle non directement prises en charge dans la plupart des langages machine: continuation de première classe; coroutine / thread; Générateur; gestion des exceptions.

Tous ces éléments nécessitent un code de support d'exécution considérable créé à l'aide de nombreuses instructions à usage général et de types de données plus élémentaires.

C a des types de données standard qui ne sont pas pris en charge par certaines machines. Depuis C99, C a des nombres complexes. Ils sont constitués de deux valeurs à virgule flottante et conçus pour fonctionner avec des routines de bibliothèque. Certaines machines n'ont pas du tout d'unité à virgule flottante.

En ce qui concerne certains types de données, ce n'est pas clair. Si une machine prend en charge l'adressage de la mémoire en utilisant un registre comme adresse de base et un autre comme un déplacement mis à l'échelle, cela signifie-t-il que les tableaux sont un type de données directement pris en charge?

Aussi, en parlant de virgule flottante, il y a une normalisation: IEEE 754 virgule flottante. La doubleraison pour laquelle votre compilateur C a un qui est d'accord avec le format à virgule flottante pris en charge par le processeur n'est pas seulement parce que les deux ont été mis d'accord, mais parce qu'il existe une norme indépendante pour cette représentation.

Kaz
la source
2

Des choses telles que

  • Listes Utilisées dans presque tous les langages fonctionnels.

  • Exceptions .

  • Tableaux associatifs (Maps) - inclus par exemple dans PHP et Perl.

  • Collecte des ordures .

  • Types de données / structures de contrôle inclus dans de nombreux langages, mais pas directement pris en charge par la CPU.

MT Tandis que
la source
2

La prise en charge directe doit être comprise comme une correspondance efficace avec le jeu d'instructions du processeur.

  • La prise en charge directe des types entiers est la règle, sauf pour les tailles longues (peuvent nécessiter des routines arithmétiques étendues) et courtes (peuvent nécessiter un masquage).

  • La prise en charge directe des types à virgule flottante nécessite qu'un FPU soit disponible.

  • La prise en charge directe des champs de bits est exceptionnelle.

  • Les structures et les tableaux nécessitent un calcul d'adresse, directement pris en charge dans une certaine mesure.

  • Les pointeurs sont toujours directement pris en charge via l'adressage indirect.

  • goto / if / while / for / do sont directement supportés par les branches inconditionnelles / conditionnelles.

  • Le commutateur peut être directement pris en charge lorsqu'une table de sauts s'applique.

  • Les appels de fonction sont directement pris en charge au moyen des fonctionnalités de pile.

Yves Daoust
la source