Après toutes ces années, le monde est toujours propulsé par la programmation C
Publié: 2022-03-11Bon nombre des projets C qui existent aujourd'hui ont été lancés il y a des décennies.
Le développement du système d'exploitation UNIX a commencé en 1969 et son code a été réécrit en C en 1972. Le langage C a en fait été créé pour déplacer le code du noyau UNIX de l'assemblage vers un langage de niveau supérieur, qui effectuerait les mêmes tâches avec moins de lignes de code. .
Le développement de la base de données Oracle a commencé en 1977 et son code a été réécrit de l'assembleur au C en 1983. Elle est devenue l'une des bases de données les plus populaires au monde.
En 1985, Windows 1.0 est sorti. Bien que le code source de Windows ne soit pas accessible au public, il a été indiqué que son noyau est principalement écrit en C, avec certaines parties en assemblage. Le développement du noyau Linux a commencé en 1991, et il est également écrit en C. L'année suivante, il a été publié sous la licence GNU et a été utilisé dans le cadre du système d'exploitation GNU. Le système d'exploitation GNU lui-même a été lancé en utilisant les langages de programmation C et Lisp, de sorte que bon nombre de ses composants sont écrits en C.
Mais la programmation en C ne se limite pas aux projets qui ont commencé il y a des décennies, quand il n'y avait pas autant de langages de programmation qu'aujourd'hui. De nombreux projets C sont encore lancés aujourd'hui ; il y a de bonnes raisons à cela.
Comment le monde est-il propulsé par C ?
Malgré la prévalence des langages de niveau supérieur, C continue d'autonomiser le monde. Voici quelques-uns des systèmes utilisés par des millions de personnes et programmés en langage C.
Microsoft Windows
Le noyau Windows de Microsoft est principalement développé en C, avec certaines parties en langage d'assemblage. Pendant des décennies, le système d'exploitation le plus utilisé au monde, avec environ 90 % des parts de marché, a été alimenté par un noyau écrit en C.
Linux
Linux est également écrit principalement en C, avec certaines parties en assemblage. Environ 97 % des 500 superordinateurs les plus puissants du monde utilisent le noyau Linux. Il est également utilisé dans de nombreux ordinateurs personnels.
Mac
Les ordinateurs Mac sont également alimentés par C, puisque le noyau OS X est écrit principalement en C. Chaque programme et pilote d'un Mac, comme dans les ordinateurs Windows et Linux, s'exécute sur un noyau alimenté en C.
Mobile
Les noyaux iOS, Android et Windows Phone sont également écrits en C. Ce ne sont que des adaptations mobiles des noyaux Mac OS, Linux et Windows existants. Ainsi, les smartphones que vous utilisez tous les jours fonctionnent sur un noyau C.
Bases de données
Les bases de données les plus populaires au monde, notamment Oracle Database, MySQL, MS SQL Server et PostgreSQL, sont codées en C (les trois premières d'entre elles étant en fait toutes deux en C et C++).
Les bases de données sont utilisées dans tous les types de systèmes : financiers, gouvernementaux, médias, divertissement, télécommunications, santé, éducation, vente au détail, réseaux sociaux, Web, etc.
Films 3D
Les films 3D sont créés avec des applications généralement écrites en C et C++. Ces applications doivent être très efficaces et rapides, car elles traitent une énorme quantité de données et effectuent de nombreux calculs par seconde. Plus ils sont efficaces, moins il faut de temps aux artistes et aux animateurs pour générer les plans du film, et plus l'entreprise économise d'argent.
Systèmes embarqués
Imaginez que vous vous réveillez un jour et que vous allez faire du shopping. Le réveil qui vous réveille est probablement programmé en C. Ensuite, vous utilisez votre micro-onde ou votre cafetière pour préparer votre petit-déjeuner. Ce sont aussi des systèmes embarqués et donc probablement programmés en C. Vous allumez votre télé ou votre radio pendant que vous prenez votre petit-déjeuner. Ce sont également des systèmes embarqués, alimentés par C. Lorsque vous ouvrez votre porte de garage avec la télécommande, vous utilisez également un système embarqué qui est très probablement programmé en C.
Ensuite, vous montez dans votre voiture. S'il possède les fonctionnalités suivantes, également programmées en C :
- transmission automatique
- systèmes de détection de la pression des pneus
- capteurs (oxygène, température, niveau d'huile, etc.)
- mémoire pour les réglages des sièges et des rétroviseurs.
- affichage du tableau de bord
- freins antiblocage
- contrôle de stabilité automatique
- régulateur de vitesse
- Contrôle du climat
- serrures à l'épreuve des enfants
- entrée sans clé
- sièges chauffants
- contrôle des airbags
Vous arrivez au magasin, garez votre voiture et allez à un distributeur automatique pour obtenir un soda. Quel langage ont-ils utilisé pour programmer ce distributeur automatique ? Probablement C. Ensuite, vous achetez quelque chose au magasin. La caisse enregistreuse est également programmée en C. Et quand vous payez avec votre carte de crédit ? Vous l'avez deviné : le lecteur de carte de crédit est, encore une fois, vraisemblablement programmé en C.
Tous ces appareils sont des systèmes embarqués. Ils sont comme de petits ordinateurs qui ont un microcontrôleur/microprocesseur à l'intérieur qui exécute un programme, également appelé micrologiciel, sur des appareils intégrés. Ce programme doit détecter les pressions sur les touches et agir en conséquence, et également afficher des informations à l'utilisateur. Par exemple, le réveil doit interagir avec l'utilisateur, détecter sur quel bouton l'utilisateur appuie et, parfois, combien de temps il est enfoncé, et programmer l'appareil en conséquence, tout en affichant à l'utilisateur les informations pertinentes. Le système de freinage antiblocage de la voiture, par exemple, doit être capable de détecter un blocage soudain des pneus et d'agir pour relâcher la pression sur les freins pendant une courte période, les déverrouillant et empêchant ainsi un dérapage incontrôlé. Tous ces calculs sont effectués par un système embarqué programmé.
Bien que le langage de programmation utilisé sur les systèmes embarqués puisse varier d'une marque à l'autre, ils sont le plus souvent programmés en langage C, en raison des caractéristiques de flexibilité, d'efficacité, de performances et de proximité du langage avec le matériel.
Pourquoi le langage de programmation C est-il toujours utilisé ?
Il existe aujourd'hui de nombreux langages de programmation qui permettent aux développeurs d'être plus productifs qu'avec C pour différents types de projets. Il existe des langages de niveau supérieur qui fournissent des bibliothèques intégrées beaucoup plus volumineuses qui simplifient le travail avec JSON, XML, l'interface utilisateur, les pages Web, les demandes des clients, les connexions à la base de données, la manipulation des médias, etc.
Mais malgré cela, il y a de nombreuses raisons de croire que la programmation en C restera active pendant longtemps.
Dans les langages de programmation, une taille unique ne convient pas à tous. Voici quelques raisons pour lesquelles C est imbattable, et presque obligatoire, pour certaines applications.
Portabilité et efficacité
C est presque un langage d'assemblage portable . Il est aussi proche que possible de la machine alors qu'il est presque universellement disponible pour les architectures de processeur existantes. Il existe au moins un compilateur C pour presque toutes les architectures existantes. Et de nos jours, en raison des binaires hautement optimisés générés par les compilateurs modernes, il n'est pas facile d'améliorer leur sortie avec un assemblage écrit à la main.
Sa portabilité et son efficacité sont telles que "les compilateurs, bibliothèques et interpréteurs d'autres langages de programmation sont souvent implémentés en C". Les langages interprétés comme Python, Ruby et PHP ont leurs principales implémentations écrites en C. Il est même utilisé par les compilateurs pour que d'autres langages communiquent avec la machine. Par exemple, C est le langage intermédiaire sous-jacent à Eiffel et Forth. Cela signifie qu'au lieu de générer du code machine pour chaque architecture à prendre en charge, les compilateurs pour ces langages génèrent simplement du code C intermédiaire et le compilateur C gère la génération du code machine.
Le C est également devenu une lingua franca pour la communication entre développeurs. Comme le dit Alex Allain, Dropbox Engineering Manager et créateur de Cprogramming.com :
C est un excellent langage pour exprimer des idées communes en programmation d'une manière qui convient à la plupart des gens. De plus, de nombreux principes utilisés en C - par exemple,
argc
etargv
pour les paramètres de ligne de commande, ainsi que les constructions de boucles et les types de variables - apparaîtront dans de nombreux autres langages que vous apprendrez, vous pourrez donc parler aux gens même s'ils ne connaissent pas C d'une manière qui vous est commune à tous les deux.
Manipulation de la mémoire
L'accès arbitraire à l'adresse mémoire et l'arithmétique des pointeurs sont une caractéristique importante qui fait du C une solution idéale pour la programmation système (systèmes d'exploitation et systèmes embarqués).
À la frontière matériel/logiciel, les systèmes informatiques et les microcontrôleurs mappent leurs périphériques et leurs broches d'E/S en adresses mémoire. Les applications système doivent lire et écrire dans ces emplacements de mémoire personnalisés pour communiquer avec le monde. La capacité de C à manipuler des adresses mémoire arbitraires est donc impérative pour la programmation système.
Un microcontrôleur pourrait être architecturé, par exemple, de telle sorte que l'octet de l'adresse mémoire 0x40008000 soit envoyé par le récepteur/émetteur asynchrone universel (ou UART, un composant matériel commun pour communiquer avec les périphériques) chaque fois que le bit numéro 4 de l'adresse 0x40008001 est défini à 1, et qu'après avoir activé ce bit, il sera automatiquement désactivé par le périphérique.
Ce serait le code d'une fonction C qui envoie un octet via cet UART :
#define UART_BYTE *(char *)0x40008000 #define UART_SEND *(volatile char *)0x40008001 |= 0x08 void send_uart(char byte) { UART_BYTE = byte; // write byte to 0x40008000 address UART_SEND; // set bit number 4 of address 0x40008001 }
La première ligne de la fonction sera étendue à :
*(char *)0x40008000 = byte;
Cette ligne indique au compilateur d'interpréter la valeur 0x40008000
comme un pointeur vers un char
, puis de déréférencer (donner la valeur pointée par) ce pointeur (avec l'opérateur *
le plus à gauche) et enfin d'attribuer une valeur d' byte
à ce pointeur déréférencé. En d'autres termes : écrivez la valeur de l' byte
variable à l'adresse mémoire 0x40008000
.

La ligne suivante sera étendue à :
*(volatile char *)0x40008001 |= 0x08;
Dans cette ligne, nous effectuons une opération OU au niveau du bit sur la valeur à l'adresse 0x40008001
et la valeur 0x08
( 00001000
en binaire, c'est-à-dire un 1 dans le bit numéro 4), et enregistrons le résultat à l'adresse 0x40008001
. Autrement dit : on positionne le bit 4 de l'octet qui se trouve à l'adresse 0x40008001. Nous déclarons également que la valeur à l'adresse 0x40008001
est volatile . Cela indique au compilateur que cette valeur peut être modifiée par des processus externes à notre code, de sorte que le compilateur ne fera aucune hypothèse sur la valeur de cette adresse après y avoir écrit. (Dans ce cas, ce bit est désactivé par le matériel UART juste après que nous l'avons défini par le logiciel.) Cette information est importante pour l'optimiseur du compilateur. Si nous faisions cela dans une boucle for
, par exemple, sans spécifier que la valeur est volatile, le compilateur pourrait supposer que cette valeur ne change jamais après avoir été définie et ignorer l'exécution de la commande après la première boucle.
Utilisation déterministe des ressources
Une fonctionnalité de langage commune sur laquelle la programmation système ne peut pas compter est la récupération de place, ou même simplement l'allocation dynamique pour certains systèmes embarqués. Les applications embarquées sont très limitées en temps et en ressources mémoire. Ils sont souvent utilisés pour les systèmes en temps réel, où un appel non déterministe au ramasse-miettes ne peut pas être autorisé. Et si l'allocation dynamique ne peut pas être utilisée en raison du manque de mémoire, il est très important d'avoir d'autres mécanismes de gestion de la mémoire, comme placer les données dans des adresses personnalisées, comme le permettent les pointeurs C. Les langages qui dépendent fortement de l'allocation dynamique et de la récupération de place ne conviendraient pas aux systèmes à ressources limitées.
Taille du code
C a un très petit temps d'exécution. Et l'empreinte mémoire de son code est plus petite que pour la plupart des autres langages.
Comparé à C++, par exemple, un binaire généré par C qui va vers un périphérique embarqué est environ la moitié de la taille d'un binaire généré par un code C++ similaire. L'une des principales causes de cela est la prise en charge des exceptions.
Les exceptions sont un excellent outil ajouté par C++ sur C, et, si elles ne sont pas déclenchées et implémentées intelligemment, elles n'ont pratiquement aucune surcharge de temps d'exécution (mais au prix d'une augmentation de la taille du code).
Voyons un exemple en C++ :
// Class A declaration. Methods defined somewhere else; class A { public: A(); // Constructor ~A(); // Destructor (called when the object goes out of scope or is deleted) void myMethod(); // Just a method }; // Class B declaration. Methods defined somewhere else; class B { public: B(); // Constructor ~B(); // Destructor void myMethod(); // Just a method }; // Class C declaration. Methods defined somewhere else; class C { public: C(); // Constructor ~C(); // Destructor void myMethod(); // Just a method }; void myFunction() { A a; // Constructor aA() called. (Checkpoint 1) { B b; // Constructor bB() called. (Checkpoint 2) b.myMethod(); // (Checkpoint 3) } // b.~B() destructor called. (Checkpoint 4) { C c; // Constructor cC() called. (Checkpoint 5) c.myMethod(); // (Checkpoint 6) } // c.~C() destructor called. (Checkpoint 7) a.myMethod(); // (Checkpoint 8) } // a.~A() destructor called. (Checkpoint 9)
Les méthodes des classes A
, B
et C
sont définies ailleurs (par exemple dans d'autres fichiers). Par conséquent, le compilateur ne peut pas les analyser et ne peut pas savoir s'ils lèveront des exceptions. Il doit donc se préparer à gérer les exceptions levées par l'un de leurs constructeurs, destructeurs ou autres appels de méthode. Les destructeurs ne doivent pas lancer (très mauvaise pratique), mais l'utilisateur peut lancer quand même, ou indirectement en appelant une fonction ou une méthode (explicitement ou implicitement) qui lève une exception.
Si l'un des appels de myFunction
une exception, le mécanisme de déroulement de la pile doit pouvoir appeler tous les destructeurs des objets déjà construits. Une implémentation du mécanisme de déroulement de la pile utilisera l'adresse de retour du dernier appel de cette fonction pour vérifier le "numéro de point de contrôle" de l'appel qui a déclenché l'exception (c'est l'explication simple). Pour ce faire, il utilise une fonction auxiliaire générée automatiquement (une sorte de table de consultation) qui sera utilisée pour le déroulement de la pile au cas où une exception serait levée depuis le corps de cette fonction, qui ressemblera à ceci :
// Possible autogenerated function void autogeneratedStackUnwindingFor_myFunction(int checkpoint) { switch (checkpoint) { // case 1 and 9: do nothing; case 3: b.~B(); goto destroyA; // jumps to location of destroyA label case 6: c.~C(); // also goes to destroyA as that is the next line destroyA: // label case 2: case 4: case 5: case 7: case 8: a.~A(); } }
Si l'exception est lancée depuis les points de contrôle 1 et 9, aucun objet n'a besoin d'être détruit. Pour le point de contrôle 3, b
et a
doivent être détruits. Pour le point de contrôle 6, c
et a
doivent être détruits. Dans tous les cas l'ordre de destruction doit être respecté. Pour les points de contrôle 2, 4, 5, 7 et 8, seul l'objet a
doit être détruit.
Cette fonction auxiliaire ajoute de la taille au code. Cela fait partie de la surcharge d'espace que C++ ajoute au C. De nombreuses applications embarquées ne peuvent pas se permettre cet espace supplémentaire. Par conséquent, les compilateurs C++ pour les systèmes embarqués ont souvent un indicateur pour désactiver les exceptions. La désactivation des exceptions en C++ n'est pas gratuite, car la bibliothèque de modèles standard s'appuie fortement sur les exceptions pour signaler les erreurs. L'utilisation de ce schéma modifié, sans exception, nécessite plus de formation pour les développeurs C++ afin de détecter d'éventuels problèmes ou de trouver des bogues.
Et, on parle de C++, un langage dont le principe est : « Vous ne payez pas pour ce que vous n'utilisez pas. Cette augmentation de la taille binaire s'aggrave pour les autres langages qui ajoutent une surcharge supplémentaire avec d'autres fonctionnalités très utiles mais qui ne peuvent pas être offertes par les systèmes embarqués. Bien que C ne vous permette pas d'utiliser ces fonctionnalités supplémentaires, il permet une empreinte de code beaucoup plus compacte que les autres langages.
Raisons d'apprendre C
Le C n'est pas un langage difficile à apprendre, donc tous les avantages de son apprentissage seront assez bon marché. Voyons quelques-uns de ces avantages.
Lingua franca
Comme déjà mentionné, C est une lingua franca pour les développeurs. De nombreuses implémentations de nouveaux algorithmes dans des livres ou sur Internet sont d'abord (ou seulement) mises à disposition en C par leurs auteurs. Cela donne la portabilité maximale possible pour la mise en œuvre. J'ai vu des programmeurs lutter sur Internet pour réécrire un algorithme C dans d'autres langages de programmation parce qu'ils ne connaissaient pas les concepts de base du C.
Sachez que C est un langage ancien et répandu, vous pouvez donc trouver toutes sortes d'algorithmes écrits en C sur le Web. Par conséquent, vous bénéficierez très probablement de la connaissance de cette langue.
Comprendre la machine (Pensez en C)
Lorsque nous discutons du comportement de certaines portions de code, ou de certaines fonctionnalités d'autres langages, avec des collègues, nous finissons par "parler en C" : cette portion passe-t-elle un "pointeur" vers l'objet ou copie-t-elle l'objet entier ? Un "casting" pourrait-il se produire ici? Etc.
Nous discuterions (ou penserions) rarement des instructions d'assemblage qu'une portion de code exécute lors de l'analyse du comportement d'une portion de code d'un langage de haut niveau. Au lieu de cela, lorsque nous discutons de ce que fait la machine, nous parlons (ou pensons) assez clairement en C.
De plus, si vous ne pouvez pas vous arrêter et penser de cette façon à ce que vous faites, vous pouvez finir par programmer avec une sorte de superstition sur la façon dont (magiquement) les choses sont faites.
Travailler sur de nombreux projets C intéressants
De nombreux projets intéressants, des gros serveurs de base de données ou des noyaux de système d'exploitation aux petites applications embarquées que vous pouvez même faire à la maison pour votre satisfaction personnelle et votre plaisir, sont réalisés en C. Il n'y a aucune raison d'arrêter de faire des choses que vous aimez peut-être pour la seule raison que vous ne connaissez pas un langage de programmation vieux et petit, mais fort et éprouvé comme C.
Conclusion
Le langage de programmation C ne semble pas avoir de date d'expiration. Sa proximité avec le matériel, sa grande portabilité et son utilisation déterministe des ressources le rendent idéal pour le développement de bas niveau pour des éléments tels que les noyaux de système d'exploitation et les logiciels embarqués. Sa polyvalence, son efficacité et ses bonnes performances en font un excellent choix pour les logiciels de manipulation de données à haute complexité, comme les bases de données ou l'animation 3D. Le fait que de nombreux langages de programmation soient aujourd'hui meilleurs que C pour l'usage auquel ils sont destinés ne signifie pas qu'ils battent C dans tous les domaines. C est toujours inégalé lorsque la performance est la priorité.
Le monde fonctionne sur des appareils alimentés par C. Nous utilisons ces appareils tous les jours, que nous en soyons conscients ou non. C est le passé, le présent et, pour autant que nous puissions le voir, toujours l'avenir pour de nombreux domaines du logiciel.