Pourquoi le langage de programmation C règne toujours

Aucune technologie ne perdure pendant 50 ans à moins qu'elle ne fasse son travail mieux que la plupart des autres, en particulier une technologie informatique. Le langage de programmation C existe depuis 1972, et il règne toujours comme l'un des éléments fondamentaux de notre monde défini par logiciel.

Mais parfois, une technologie reste là parce que les gens ne sont tout simplement pas parvenus à la remplacer. Au cours des dernières décennies, des dizaines d'autres langages sont apparus - certains explicitement conçus pour contester la domination de C, certains rognant sur C par le côté comme un sous-produit de leur popularité.

Il n'est pas difficile de dire que C doit être remplacé. La recherche en langage de programmation et les pratiques de développement de logiciels suggèrent toutes qu'il existe de bien meilleures façons de faire les choses que celle de C. Mais C persiste tout de même, avec des décennies de recherche et développement derrière lui. Peu d'autres langages peuvent le battre en termes de performances, de compatibilité bare-metal ou d'ubiquité. Néanmoins, cela vaut la peine de voir comment C se compare à la concurrence linguistique de grands noms en 2018.

C contre C ++

Naturellement, le C est le plus souvent comparé au C ++, le langage qui, comme son nom l'indique, a été créé comme une extension de C. Les différences entre C ++ et C pourraient être qualifiées d'étendues ou  excessives , selon la personne à qui vous le demandez.

Tout en restant semblable à C dans sa syntaxe et son approche, C ++ fournit de nombreuses fonctionnalités réellement utiles qui ne sont pas disponibles nativement en C: espaces de noms, modèles, exceptions, gestion automatique de la mémoire, etc. Les projets qui exigent des performances de haut niveau (bases de données, systèmes d'apprentissage automatique) sont fréquemment écrits en C ++ en utilisant ces fonctionnalités pour éliminer chaque baisse de performance du système.

En outre, C ++ continue de se développer de manière beaucoup plus agressive que C. Le prochain C ++ 20 apporte encore plus à la table, y compris des modules, des coroutines, une bibliothèque de synchronisation et des concepts, qui facilitent l'utilisation des modèles. La dernière révision de la norme C ajoute peu et se concentre sur le maintien de la compatibilité ascendante.

Le fait est que tous les avantages de C ++ peuvent également fonctionner comme des inconvénients. De grands. Plus vous utilisez de fonctionnalités C ++, plus vous introduisez de complexité et plus il devient difficile d'apprivoiser les résultats. Les développeurs qui se limitent à un sous-ensemble de C ++ peuvent éviter bon nombre de ses pires pièges et excès. Mais certains magasins veulent se prémunir contre la complexité C ++. Rester avec C oblige les développeurs à se limiter à ce sous-ensemble. L'équipe de développement du noyau Linux, par exemple, évite le C ++.

Choisir C plutôt que C ++ est un moyen pour vous, et pour tous les développeurs qui maintiennent le code après vous, d'éviter d'avoir à vous mêler des excès C ++, en adoptant un minimalisme imposé. Bien sûr, C ++ dispose d'un riche ensemble de fonctionnalités de haut niveau pour une bonne raison. Mais si le minimalisme convient mieux aux projets actuels et futurs - et aux équipes de projet - alors C a plus de sens.

C contre Java

Après des décennies, Java reste un élément de base du développement de logiciels d'entreprise et un élément de base du développement en général. La plupart des projets logiciels d'entreprise les plus importants ont été écrits en Java - y compris la grande majorité des projets Apache Software Foundation - et Java reste un langage viable pour le développement de nouveaux projets répondant à des exigences professionnelles.

La syntaxe Java emprunte beaucoup au C et au C ++. Contrairement à C, cependant, Java ne compile pas par défaut en code natif. Au lieu de cela, l'environnement d'exécution Java, la JVM, JIT (juste à temps) compile le code Java à exécuter dans l'environnement cible. Dans les bonnes circonstances, le code Java JITted peut approcher ou même dépasser les performances de C.

La philosophie «écrire une fois, exécuter n'importe où» derrière Java permet également aux programmes Java de s'exécuter avec relativement peu de réglages pour une architecture cible. En revanche, bien que C ait été porté sur un grand nombre d'architectures, tout programme C donné peut encore avoir besoin de personnalisation pour fonctionner correctement sous, par exemple, Windows par rapport à Linux.

Cette combinaison de portabilité et de performances élevées, ainsi qu'un énorme écosystème de bibliothèques de logiciels et de frameworks, font de Java un langage et un environnement d'exécution incontournables pour la création d'applications d'entreprise.

Là où Java est en deçà de C, c'est un domaine où Java n'a jamais été censé rivaliser: fonctionner à proximité du métal ou travailler directement avec du matériel. Le code C est compilé en code machine, qui est exécuté directement par le processus. Java est compilé en bytecode, qui est un code intermédiaire que l'interpréteur JVM convertit ensuite en code machine. De plus, bien que la gestion automatique de la mémoire par Java soit une bénédiction dans la plupart des cas, C est mieux adapté aux programmes qui doivent utiliser de manière optimale des ressources mémoire limitées.

Cela dit, il existe certains domaines dans lesquels Java peut se rapprocher de C en termes de vitesse. Le moteur JIT de la JVM optimise les routines au moment de l'exécution en fonction du comportement du programme, ce qui permet de nombreuses classes d'optimisation qui ne sont pas possibles avec le C. compilé à l'avance. Par exemple, Apache Spark optimise le traitement en mémoire en partie en utilisant un code de gestion de mémoire personnalisé qui contourne la JVM.

C contre C # et .Net

Près de deux décennies après leur introduction, C # et .Net Framework restent des éléments majeurs du monde des logiciels d'entreprise. On a dit que C # et .Net étaient la réponse de Microsoft à Java - un système de compilation de code managé et un environnement d'exécution universel - et tant de comparaisons entre C et Java sont également valables pour C et C # /. Net.

Comme Java (et dans une certaine mesure Python), .Net offre une portabilité sur une variété de plates-formes et un vaste écosystème de logiciels intégrés. Ce ne sont pas de petits avantages compte tenu de l'ampleur du développement orienté entreprise dans le monde .Net. Lorsque vous développez un programme en C #, ou dans tout autre langage .Net, vous pouvez vous inspirer d'un univers d'outils et de bibliothèques écrits pour le runtime .Net. 

Un autre avantage .NET de type Java est l'optimisation JIT. Les programmes C # et .Net peuvent être compilés à l'avance selon C, mais ils sont principalement compilés juste à temps par le runtime .Net et optimisés avec les informations d'exécution. La compilation JIT permet toutes sortes d'optimisations sur place pour un programme .Net en cours d'exécution qui ne peuvent pas être effectuées en C.

Comme C, C # et .Net fournissent divers mécanismes pour accéder directement à la mémoire. Le tas, la pile et la mémoire système non gérée sont tous accessibles via des API et des objets .Net. Et les développeurs peuvent utiliser le unsafemode de .Net pour obtenir des performances encore meilleures.

Rien de tout cela n'est gratuit, cependant. Les objets gérés et les unsafeobjets ne peuvent pas être échangés arbitrairement, et le marshaling entre eux a un coût en termes de performances. Par conséquent, maximiser les performances des applications .Net signifie réduire au minimum les mouvements entre les objets gérés et non gérés.

Lorsque vous ne pouvez pas vous permettre de payer la pénalité pour la mémoire gérée par rapport à la mémoire non gérée, ou lorsque le runtime .Net est un mauvais choix pour l'environnement cible (par exemple, l'espace du noyau) ou peut ne pas être disponible du tout, alors C est ce que vous avoir besoin. Et contrairement à C # et .Net, C déverrouille l'accès direct à la mémoire par défaut. 

C contre Go

La syntaxe Go doit beaucoup au C: accolades comme délimiteurs, instructions terminées par des points-virgules, etc. Les développeurs compétents en C peuvent généralement sauter directement dans Go sans trop de difficulté, même en tenant compte des nouvelles fonctionnalités de Go telles que les espaces de noms et la gestion des paquets.

Un code lisible était l'un des objectifs de conception de Go: permettre aux développeurs de se familiariser facilement avec n'importe quel projet Go et de maîtriser la base de code en peu de temps. Les bases de code C peuvent être difficiles à gérer, car elles ont tendance à se transformer en un nid de macros et #ifdefsont spécifiques à la fois à un projet et à une équipe donnée. La syntaxe de Go, et ses outils intégrés de formatage de code et de gestion de projet, sont destinés à garder ce genre de problèmes institutionnels à distance.

Go propose également des extras tels que des goroutines et des canaux, des outils de niveau langue pour gérer la concurrence et le passage de messages entre les composants. C exigerait que de tels éléments soient déroulés à la main ou fournis par une bibliothèque externe, mais Go les fournit dès la sortie de la boîte, ce qui facilite grandement la construction de logiciels qui en ont besoin.

Là où Go diffère le plus de C sous le capot, c'est dans la gestion de la mémoire. Les objets Go sont automatiquement gérés et récupérés par défaut. Pour la plupart des travaux de programmation, c'est extrêmement pratique. Mais cela signifie également que tout programme nécessitant une gestion déterministe de la mémoire sera plus difficile à écrire.

Go inclut le unsafepackage permettant de contourner certaines des sécurités de gestion des types de Go, telles que la lecture et l'écriture de mémoire arbitraire avec un Pointertype. Mais unsafevient avec un avertissement que les programmes écrits avec lui «peuvent ne pas être portables et ne sont pas protégés par les directives de compatibilité Go 1».

Go est bien adapté pour créer des programmes tels que des utilitaires de ligne de commande et des services réseau, car ils ont rarement besoin de manipulations aussi fines. Mais les pilotes de périphérique de bas niveau, les composants du système d'exploitation de l'espace noyau et d'autres tâches qui exigent un contrôle rigoureux de la disposition et de la gestion de la mémoire sont mieux créés en C.

C contre rouille

À certains égards, Rust est une réponse aux énigmes de gestion de la mémoire créées par C et C ++, ainsi qu'à de nombreuses autres lacunes de ces langages. Rust compile en code machine natif, il est donc considéré sur un pied d'égalité avec C en ce qui concerne les performances. La sécurité de la mémoire par défaut, cependant, est le principal argument de vente de Rust.

La syntaxe et les règles de compilation de Rust aident les développeurs à éviter les erreurs courantes de gestion de la mémoire. Si un programme a un problème de gestion de la mémoire qui traverse la syntaxe Rust, il ne se compilera tout simplement pas. Les nouveaux venus dans le langage, en particulier dans un langage comme C qui offre beaucoup de place pour de tels bogues, passent la première phase de leur formation Rust à apprendre à apaiser le compilateur. Mais les partisans de Rust soutiennent que cette douleur à court terme a un bénéfice à long terme: un code plus sûr qui ne sacrifie pas la vitesse.

Rust améliore également C avec son outillage. La gestion des projets et des composants fait partie de la chaîne d'outils fournie par défaut avec Rust, comme avec Go. Il existe un moyen recommandé par défaut de gérer les packages, d'organiser les dossiers de projet et de gérer de nombreuses autres choses qui, en C, sont au mieux ad hoc, chaque projet et chaque équipe les traitant différemment.

Pourtant, ce qui est présenté comme un avantage dans Rust peut ne pas sembler être un avantage pour un développeur C. Les fonctionnalités de sécurité de la compilation de Rust ne peuvent pas être désactivées, de sorte que même le programme Rust le plus trivial doit se conformer aux restrictions de sécurité de la mémoire de Rust. C peut être moins sûr par défaut, mais il est beaucoup plus flexible et indulgent lorsque cela est nécessaire.

Un autre inconvénient possible est la taille du langage Rust. C a relativement peu de fonctionnalités, même en tenant compte de la bibliothèque standard. L'ensemble de fonctionnalités de Rust est tentaculaire et continue de croître. Comme avec C ++, le plus grand ensemble de fonctionnalités de Rust signifie plus de puissance, mais aussi plus de complexité. C est un langage plus petit, mais beaucoup plus facile à modéliser mentalement, donc peut-être mieux adapté aux projets où Rust serait excessif.

C contre Python

De nos jours, chaque fois que l'on parle de développement logiciel, Python semble toujours entrer dans la conversation. Après tout, Python est «le deuxième meilleur langage pour tout», et incontestablement l'un des plus polyvalents, avec des milliers de bibliothèques tierces disponibles.

Ce que Python met l'accent, et là où il diffère le plus de C, est de favoriser la vitesse de développement plutôt que la vitesse d'exécution. Un programme qui peut prendre une heure à être assemblé dans un autre langage, comme C, peut être assemblé en Python en quelques minutes. D'un autre côté, ce programme peut prendre quelques secondes pour s'exécuter en C, mais une minute pour s'exécuter en Python. (Une bonne règle de base: les programmes Python exécutent généralement un ordre de grandeur plus lent que leurs homologues C.) Mais pour de nombreux travaux sur du matériel moderne, Python est assez rapide, et cela a été la clé de son adoption.

Une autre différence majeure est la gestion de la mémoire. Les programmes Python sont entièrement gérés en mémoire par le runtime Python, de sorte que les développeurs n'ont pas à se soucier des détails de l'allocation et de la libération de mémoire. Mais là encore, la facilité des développeurs se fait au détriment des performances d'exécution. L'écriture de programmes C nécessite une attention scrupuleuse à la gestion de la mémoire, mais les programmes résultants sont souvent la référence en matière de vitesse de machine pure.

Sous la peau, cependant, Python et C partagent une connexion profonde: le runtime de référence Python est écrit en C. Cela permet aux programmes Python d'encapsuler des bibliothèques écrites en C et C ++. Des segments importants de l'écosystème Python de bibliothèques tierces, comme pour l'apprentissage automatique, ont du code C au cœur.

Si la vitesse de développement compte plus que la vitesse d'exécution, et si la plupart des parties performantes du programme peuvent être isolées en composants autonomes (au lieu d'être réparties dans tout le code), soit du Python pur, soit un mélange de bibliothèques Python et C font un meilleur choix que C seul. Sinon, C règne toujours.