La vraie raison se résume à une différence fondamentale d'intention entre C et C ++, d'une part, et Java et C # (pour quelques exemples seulement), d'autre part. Pour des raisons historiques, une grande partie de la discussion ici porte sur le C plutôt que sur le C ++, mais (comme vous le savez probablement déjà), le C ++ est un descendant assez direct du C, aussi ce qu'il dit à propos de C s'applique également au C ++.
Bien qu'ils soient en grande partie oubliés (et leur existence parfois même niée), les toutes premières versions d'UNIX ont été écrites en langage assembleur. Une grande partie (sinon uniquement) de l'objectif initial de C était de transférer UNIX du langage d'assemblage à un langage de niveau supérieur. Une partie de l’intention était d’écrire le plus possible le système d’exploitation dans un langage de niveau supérieur - ou de le regarder dans l’autre sens, afin de minimiser la quantité qui devait être écrite en langage assembleur.
Pour ce faire, C devait fournir à peu près le même niveau d'accès au matériel que le langage d'assemblage. Le PDP-11 (pour un exemple) a mappé des registres d'E / S à des adresses spécifiques. Par exemple, vous liriez un emplacement de mémoire pour vérifier si une touche avait été enfoncée sur la console système. Un bit a été placé à cet endroit lorsqu'il y avait des données en attente de lecture. Vous liriez ensuite un octet depuis un autre emplacement spécifié pour récupérer le code ASCII de la touche sur laquelle vous avez appuyé.
De même, si vous souhaitez imprimer des données, vous devez vérifier un autre emplacement spécifié et, lorsque le périphérique de sortie est prêt, vous écrivez vos données dans un autre emplacement spécifié.
Pour prendre en charge l’écriture de pilotes pour de tels périphériques, C vous permettait de spécifier un emplacement quelconque en utilisant un type entier, de le convertir en pointeur et de lire ou d’écrire cet emplacement en mémoire.
Bien sûr, cela pose un problème assez sérieux: toutes les machines sur terre n’ont pas leur mémoire identique à celle d’un PDP-11 du début des années 1970. Ainsi, lorsque vous prenez cet entier, que vous le convertissez en un pointeur, puis que vous lisez ou écrivez via ce pointeur, personne ne peut fournir de garantie raisonnable quant à ce que vous allez obtenir. Juste pour un exemple évident, la lecture et l’écriture peuvent mapper des registres séparés dans le matériel. Ainsi, contrairement à la mémoire normale, si vous écrivez quelque chose, puis essayez de le relire, ce que vous lisez peut ne pas correspondre à ce que vous avez écrit.
Je peux voir quelques possibilités qui nous laissent:
- Définir une interface pour tout le matériel possible - spécifiez les adresses absolues de tous les emplacements que vous souhaitez lire ou écrire pour interagir avec le matériel de quelque manière que ce soit.
- Interdire ce niveau d'accès et décréter que quiconque veut faire de telles choses doit utiliser le langage assembleur.
- Autorisez les utilisateurs à le faire, mais laissez-leur le soin de lire (par exemple) les manuels du matériel ciblé et écrivez le code correspondant au matériel utilisé.
Parmi ceux-ci, 1 semble suffisamment absurde pour que nous n’ayions pas besoin de poursuivre la discussion. 2 consiste essentiellement à jeter l’intention fondamentale de la langue. Cela laisse la troisième option essentiellement la seule option qu’ils pourraient raisonnablement envisager.
Un autre point qui revient assez souvent est la taille des types entiers. C prend la "position" qui int
devrait être la taille naturelle suggérée par l'architecture. Donc, si je programme un VAX 32 bits, cela int
devrait probablement être 32 bits, mais si je programme un Univac 36 bits, cela int
devrait probablement être 36 bits (et ainsi de suite). Il n'est probablement pas raisonnable (et même impossible) d'écrire un système d'exploitation pour un ordinateur 36 bits en utilisant uniquement des types dont la taille est garantie être un multiple de 8 bits. Je suis peut-être superficiel, mais il me semble que si j'écrivais un système d'exploitation pour une machine 36 bits, je préférerais probablement utiliser un langage prenant en charge le type 36 bits.
Du point de vue de la langue, cela conduit à un comportement encore plus indéfini. Si je prends la plus grande valeur qui puisse tenir dans 32 bits, qu’arrivera-t-il si j’ajoute 1? Sur un matériel 32 bits typique, il va basculer (ou éventuellement renvoyer une sorte de défaillance matérielle). D'un autre côté, s'il fonctionne sur du matériel 36 bits, il vous suffira ... d'en ajouter un. Si le langage prend en charge l’écriture de systèmes d’exploitation, vous ne pouvez garantir aucun comportement: vous devez autoriser à la fois la taille des types et le comportement du débordement à varier.
Java et C # peuvent ignorer tout cela. Ils ne sont pas conçus pour prendre en charge l'écriture de systèmes d'exploitation. Avec eux, vous avez plusieurs choix. L’une consiste à faire en sorte que le matériel supporte ce qu’ils exigent - car ils exigent des types de 8, 16, 32 et 64 bits, il suffit de créer du matériel qui prend en charge ces tailles. L'autre possibilité évidente est que la langue ne s'exécute que sur d'autres logiciels fournissant l'environnement qu'ils souhaitent, quel que soit le matériel sous-jacent.
Dans la plupart des cas, ce n'est pas vraiment un choix. Au contraire, de nombreuses implémentations font un peu des deux. Vous exécutez normalement Java sur une machine virtuelle Java s'exécutant sur un système d'exploitation. Le plus souvent, le système d'exploitation est écrit en C et la JVM en C ++. Si la machine virtuelle Java s'exécute sur un processeur ARM, il est fort probable que le processeur intègre les extensions Jazelle d'ARM, afin d'adapter le matériel aux besoins de Java. Il est donc inutile de faire des logiciels et le code Java s'exécute plus rapidement (ou moins). lentement, quand même).
Sommaire
C et C ++ ont un comportement indéfini, car personne n'a défini d'alternative acceptable lui permettant de faire ce qu'il est censé faire. C # et Java adoptent une approche différente, mais cette approche correspond mal (voire pas du tout) aux objectifs de C et C ++. En particulier, ni l'un ni l'autre ne semble constituer un moyen raisonnable d'écrire un logiciel système (tel qu'un système d'exploitation) sur le matériel le plus arbitrairement choisi. Les deux dépendent généralement des fonctionnalités fournies par le logiciel système existant (généralement écrit en C ou C ++) pour effectuer leur travail.