Le langage C répond à une difficulté concrète des années 1970 : comment
écrire un système d’exploitation assez proche du matériel pour rester
efficace, sans devoir le recommencer presque entièrement à chaque
changement d’ordinateur ?
Dennis Ritchie développe C aux Bell Labs
dans le contexte d’Unix. Le langage ne cherche pas à dissimuler la
machine. Il fournit plutôt un petit ensemble d’abstractions — types,
fonctions, structures et pointeurs — que le compilateur peut traduire
efficacement sur plusieurs architectures. Ce compromis explique sa
diffusion, mais aussi les erreurs de mémoire que des langages plus
récents cherchent à empêcher.
Avant C : BCPL et B
Dans les années 1960, les logiciels système sont encore largement écrits
en assembleur. Ce langage permet de contrôler précisément le processeur,
mais chaque architecture possède ses propres instructions. Porter un
programme signifie souvent en réécrire une grande partie.
Martin Richards conçoit BCPL en 1967 comme un langage compact
destiné notamment aux compilateurs et aux systèmes. Ken
Thompson en dérive B pour les premières
versions d’Unix. B simplifie le travail par rapport à l’assembleur, mais
son modèle essentiellement non typé a été pensé pour des machines
organisées autour de mots mémoire.
Le PDP-11 sur lequel Unix évolue peut adresser des octets et manipuler
plusieurs tailles de données. Ritchie ajoute des types, améliore les
tableaux et les structures, puis transforme progressivement le « New B »
en C entre 1971 et 1973.
Un langage façonné par Unix
C n’est pas conçu sur le papier puis appliqué à Unix. Le langage et le
système progressent ensemble. Les besoins du noyau poussent C à
représenter des adresses, des caractères, des blocs de mémoire et des
structures de données avec peu de surcoût.
Un pointeur contient l’adresse d’une donnée. Il permet de parcourir
un tableau, de construire une liste chaînée ou d’accéder à un
périphérique. Cette souplesse ne garantit pas que l’adresse soit valide.
Un programme peut lire au-delà d’un tableau, utiliser une zone déjà
libérée ou interpréter des octets sous un type incorrect.
Le langage laisse également certains détails à l’implantation : taille
des entiers, ordre des octets ou alignement des structures. Un programme
portable doit respecter les garanties définies par C plutôt que dépendre
des particularités observées sur une machine.
La réécriture d’Unix change la portée du projet
En 1973, l’essentiel du noyau Unix est réécrit en C. Quelques portions
dépendantes du matériel restent en assembleur, mais la majeure partie du
système peut être recompilée pour une nouvelle architecture.
L’événement ne signifie pas que C rend tout programme automatiquement
portable. Les pilotes et les hypothèses matérielles demandent toujours
une adaptation. Il montre néanmoins qu’un système d’exploitation
performant peut être exprimé dans un langage de plus haut niveau sans
devenir prisonnier d’un processeur.
Unix se diffuse ensuite dans les universités avec son code source et ses
outils. Les étudiants apprennent le système et son langage principal,
écrivent de nouveaux programmes puis emportent ces pratiques dans
l’industrie. C bénéficie de l’adoption d’Unix ; Unix devient plus
adaptable grâce à C.
Une syntaxe réduite, une bibliothèque essentielle
C fournit peu de constructions intégrées. Les entrées-sorties, la
manipulation des chaînes et l’allocation dynamique viennent
principalement de bibliothèques. Cette séparation maintient le cœur du
langage compact et permet de l’implanter dans des environnements très
différents.
La bibliothèque standard établit cependant un contrat indispensable. Une
fonction telle que fopen évite à chaque programme de connaître
l’interface exacte du système pour ouvrir un fichier. La portabilité
repose donc sur trois éléments : le langage, son compilateur et une
bibliothèque compatible.
La compilation produit généralement du code natif. C n’impose ni machine
virtuelle ni ramasse-miettes. Le programmeur contrôle la durée de vie
des allocations, ce qui favorise des empreintes prévisibles mais
augmente la responsabilité concernant les ressources.
Du « K&R C » à la norme internationale
En 1978, Brian Kernighan et Dennis
Ritchie publient The C Programming Language. L’ouvrage décrit le
langage de manière assez précise pour devenir sa référence de fait.
Cette variante est souvent appelée K&R C.
La multiplication des compilateurs révèle progressivement des
divergences. Un comité de l’ANSI commence en 1983 à définir une norme
commune. C89 formalise le langage et sa bibliothèque ; l’ISO
l’adopte en 1990.
Les révisions suivantes ajoutent prudemment de nouveaux types, des
outils pour la programmation concurrente ou des clarifications. Cette
évolution reste contrainte par la compatibilité : d’immenses bases de
code et de nombreuses interfaces système dépendent du comportement
historique de C.
Pourquoi C a influencé autant de langages
C++ part directement de C pour ajouter des
abstractions plus riches. Objective-C lui associe un modèle d’objets
inspiré de Smalltalk. Java, JavaScript, C# et Go reprennent une partie
de sa syntaxe sans conserver le même modèle de mémoire.
Cette influence syntaxique ne doit pas masquer des contrats différents.
Java protège davantage les accès mémoire au moyen d’une machine
virtuelle. Rust cherche à garantir les durées de vie par son système de
types. Go utilise un ramasse-miettes. Tous répondent, à leur manière,
aux coûts du contrôle direct offert par C.
C reste une interface commune dans les systèmes d’exploitation, les
bibliothèques et les environnements embarqués. Même un langage qui
protège sa mémoire doit souvent communiquer avec une API conçue en C.
Les limites de la confiance accordée au programmeur
Le standard C définit des comportements indéfinis : lorsqu’un
programme viole certaines règles, aucune réponse n’est imposée. Le
compilateur peut alors optimiser en supposant que ces situations ne se
produisent pas. Cette liberté favorise les performances, mais rend
certaines erreurs difficiles à prévoir.
Les dépassements de tampon et erreurs de durée de vie ont alimenté de
nombreuses vulnérabilités. Compilateurs, analyseurs statiques,
protections d’exécution et règles de codage réduisent les risques sans
les supprimer. C demeure pertinent lorsqu’un contrôle précis et une
large compatibilité sont indispensables ; il n’est pas pour autant le
choix par défaut de tout logiciel.
Pourquoi C compte encore
C a établi un niveau d’abstraction particulièrement durable entre
assembleur et logiciels de haut niveau. Il permet de décrire des
opérations proches du matériel avec une notation que plusieurs
architectures peuvent partager.
Son histoire enseigne qu’une abstraction réussie ne cache pas
nécessairement tous les coûts. Elle peut aussi les rendre manipulables.
Le prix de cette transparence est une discipline que le langage impose
peu lui-même et que les équipes, outils et standards doivent apporter.
Chronologie
- 1967 : Martin Richards présente BCPL.
- 1969–1970 : Ken Thompson développe B pour les premiers outils
d’Unix.
- 1971–1972 : Dennis Ritchie fait évoluer B vers C sur le PDP-11.
- 1973 : l’essentiel du noyau Unix est réécrit en C.
- 1978 : publication de The C Programming Language.
- 1983 : début des travaux du comité ANSI X3J11.
- 1989 : publication de la norme ANSI C.
- 1990 : adoption de C comme norme ISO.
- 1999 : C99 ajoute notamment de nouveaux types et plusieurs
outils numériques.
- 2011 : C11 introduit entre autres un modèle de mémoire pour la
concurrence.
- Aujourd’hui : C reste central dans les systèmes, l’embarqué et
les interfaces logicielles.
Questions fréquentes
Dennis Ritchie a-t-il créé C seul ?
Il en est le principal concepteur. Le langage naît toutefois dans
l’environnement collectif des Bell Labs, au contact de B, d’Unix, de ses
utilisateurs et de ses compilateurs.
C est-il portable par nature ?
Il facilite la portabilité, mais un programme peut dépendre de détails
matériels ou d’extensions. La portabilité réelle exige de respecter le
standard et d’isoler le code spécifique à une plateforme.
Quelle différence existe entre C et C++ ?
C++ dérive de C et ajoute notamment classes, templates, surcharge et
gestion déterministe des ressources. Les deux langages possèdent
aujourd’hui des normes distinctes et ne sont pas totalement compatibles.
Pourquoi C n’a-t-il pas de ramasse-miettes ?
Sa conception privilégie un environnement d’exécution réduit et le
contrôle explicite des ressources. Une implantation peut ajouter un
collecteur, mais le langage standard ne l’impose pas.
C est-il encore un bon choix pour commencer ?
Il enseigne la représentation des données et le fonctionnement de la
mémoire, mais expose rapidement des risques complexes. Sa pertinence
dépend de l’objectif pédagogique et du domaine visé.
C va-t-il être remplacé par des langages plus sûrs ?
Certains nouveaux projets privilégient Rust ou d’autres langages lorsque
la sûreté mémoire est prioritaire. L’étendue du code, des outils et des
interfaces existants garantit néanmoins à C une longue période de
coexistence.