Langage C

Le langage C

Le nom C n’est pas un sigle. Il succède au langage B, lui-même issu de BCPL.

Le langage qui a rendu Unix portable

Au début des années 1970, programmer un système d’exploitation signifie encore travailler presque entièrement en assembleur. Cette méthode permet de contrôler précisément la machine, mais elle exige beaucoup de temps et lie étroitement le programme au processeur pour lequel il a été écrit. Changer d’ordinateur oblige souvent à reprendre une grande partie du travail. Aux Bell Laboratories, Dennis Ritchie et Ken Thompson sont alors engagés dans le développement d’un nouveau système : Unix. Les premières versions fonctionnent sur un DEC PDP-7 et sont écrites en assembleur. Le système reste modeste, mais son développement fait apparaître un besoin essentiel : disposer d’un langage suffisamment proche de la machine pour écrire un noyau, tout en étant plus lisible et plus facile à modifier que l’assembleur. C’est dans ce contexte que naît le langage C. Il n’est pas conçu comme un exercice théorique ni comme un langage destiné en priorité à l’enseignement. Il apparaît comme un outil de travail, façonné pour construire Unix et ses utilitaires. Cette origine explique une grande partie de ses caractéristiques : le C est compact, efficace et donne au programmeur un accès direct à la mémoire.

Date d’apparition : 1972

  • Milieu des années 1960 : Martin Richards conçoit BCPL, un langage compact destiné notamment à la programmation de systèmes.
  • 1969–1970 : Ken Thompson crée le langage B à partir de BCPL pour l’environnement Unix naissant.
  • 1971 : Dennis Ritchie commence à ajouter des types à B et développe une étape intermédiaire brièvement appelée NB, pour New B.
  • 1972 : la période la plus créative de la mise au point du C aboutit aux éléments essentiels du nouveau langage.
  • Été 1973 : le noyau d’Unix est réécrit en C sur le DEC PDP-11.
  • 1978 : Brian Kernighan et Dennis Ritchie publient The C Programming Language, bientôt désigné par leurs initiales : le K&R.
  • 1989 : le comité ANSI X3J11 publie la première norme officielle du langage.

Dennis Ritchie situe la formation du C entre 1969 et 1973, avec un moment décisif en 1972. Il est donc plus exact de parler d’une évolution progressive que d’une invention survenue en une seule journée. Le C se construit au rythme des besoins d’Unix, des progrès de son compilateur et des possibilités offertes par le PDP-11.

À quel besoin répond le langage C ?

Le C répond d’abord à une question très concrète : comment écrire Unix autrement qu’en assembleur sans perdre trop de performances ?

L’assembleur est rapide et précis, mais chaque famille de machines possède son propre jeu d’instructions. Un programme écrit pour le PDP-7 ne peut pas être simplement recompilé pour le PDP-11. Un langage de plus haut niveau facilite la lecture et la modification du programme, mais son compilateur doit produire un code suffisamment compact et rapide pour un système d’exploitation.

Les premières versions de B utilisées sur le PDP-7 produisent du code interprété, sensiblement plus lent que l’assembleur. Elles ne semblent donc pas adaptées à la réécriture complète d’Unix.

Dennis Ritchie fait alors évoluer à la fois le langage et son compilateur. Le nouveau compilateur génère directement des instructions pour le PDP-11. Le C peut ainsi rivaliser avec l’assembleur pour de nombreuses tâches tout en offrant une expression plus structurée du programme.

Avant le C : BCPL et B

La filiation du langage peut être résumée ainsi :

BCPL  →  B  →  C

BCPL, conçu par Martin Richards, est un langage volontairement simple. B, développé principalement par Ken Thompson, en reprend l’esprit en l’adaptant aux moyens très limités disponibles pour le premier Unix.

BCPL et B ne distinguent pas fortement les types de données. Une cellule mémoire contient un mot, et c’est l’opération effectuée qui détermine la manière d’interpréter son contenu. Le même motif de bits peut donc servir d’entier, de caractère ou d’adresse.

Cette simplicité convient au PDP-7, mais elle devient gênante sur le PDP-11. Cette nouvelle machine travaille avec des mots de 16 bits tout en permettant l’adressage séparé des octets. Un caractère, un entier et une adresse ne se manipulent donc plus exactement de la même manière. Le langage doit connaître la nature de la donnée afin de produire les bonnes instructions.

Ritchie introduit alors des types comme char et int, puis des pointeurs associés à ces types. La première version enrichie de B est brièvement appelée NB, pour New B. Lorsque le langage devient suffisamment différent, Ritchie le nomme C, dans la continuité de B. Contrairement à BASIC ou FORTRAN, C n’est donc pas un acronyme.

Un langage compilé, impératif et structuré

Un programme C décrit une suite d’opérations regroupées dans des fonctions.
Le programmeur utilise des variables, des tests et des boucles pour indiquer ce que la machine doit faire.

Voici un exemple simple :

#include <stdio.h>

int main(void)
{
    int i;
    int somme = 0;

    for (i = 1; i <= 10; i++) {
        somme = somme + i;
    }

    printf("Somme : %d\n", somme);
    return 0;
}

Ce programme additionne les nombres de 1 à 10, puis affiche le résultat.

Quelques éléments caractéristiques apparaissent immédiatement :

  • int indique qu’une variable ou qu’une fonction utilise un entier ;
  • main est la fonction par laquelle commence l’exécution du programme ;
  • for décrit une boucle ;
  • les accolades regroupent plusieurs instructions ;
  • le point-virgule termine la plupart des instructions ;
  • printf appartient à la bibliothèque standard et permet ici d’afficher le résultat.

Le fichier source n’est pas directement exécuté par le processeur. Le préprocesseur traite notamment les directives comme #include, le compilateur traduit le C, puis l’éditeur de liens réunit le programme et les bibliothèques nécessaires pour produire un exécutable.

L’introduction des types constitue une différence essentielle avec B. Le programmeur peut indiquer la nature des données :

char lettre = 'C';
int compteur = 10;
float temperature = 21.5f;

Le type informe le compilateur sur la représentation de la valeur et sur les opérations possibles. Les premières versions du C restent toutefois moins strictes que les compilateurs modernes et vérifient imparfaitement certains usages. Le contrôle des types se renforcera progressivement.

Les pointeurs

Le pointeur est l’un des mécanismes les plus caractéristiques du C. Il s’agit d’une variable dont la valeur désigne l’adresse d’une autre donnée.

int valeur = 42;
int *p = &valeur;

*p = 50;

Dans cet exemple :

  • &valeur obtient l’adresse de la variable valeur ;
  • p conserve cette adresse ;
  • *p permet d’accéder à la donnée située à cette adresse ;
  • l’instruction *p = 50 modifie donc directement valeur.

Pourquoi ne pas inscrire directement une adresse précise dans le programme ? Parce que l’emplacement d’une variable peut changer d’une exécution ou d’une machine à l’autre. Le pointeur permet de travailler avec une adresse obtenue au moment approprié, sans connaître à l’avance sa valeur numérique.

Cette indirection sert notamment à construire des structures dynamiques, parcourir des zones mémoire ou modifier une donnée depuis une fonction. Elle offre une grande puissance, mais impose aussi de la prudence : une adresse invalide peut provoquer une corruption de mémoire ou l’arrêt du programme.

Tableaux et pointeurs : une relation étroite

En C, les tableaux et les pointeurs sont étroitement liés. Dans la plupart des expressions, le nom d’un tableau est converti en pointeur vers son premier élément.

int notes[3] = {12, 15, 18};

printf("%d\n", notes[1]);
printf("%d\n", *(notes + 1));

Les deux affichages donnent ici la même valeur : 15. L’écriture notes[1] est une notation plus lisible pour accéder au deuxième élément, tandis que *(notes + 1) montre le calcul d’adresse sous-jacent.

Ce choix permet de décrire et de parcourir efficacement les tableaux avec peu de règles. En contrepartie, la frontière entre le tableau et l’adresse de ses données peut sembler déroutante. Ritchie reconnaît lui-même que cette relation est à la fois une force du langage et l’une de ses caractéristiques les plus critiquées.

Une chaîne de caractères est un tableau

Le C d’origine ne possède pas de type spécifique pour représenter une chaîne de caractères. Une chaîne est un tableau de caractères terminé par une valeur nulle :

char nom[] = "UNIX";

En mémoire, ce tableau contient :

'U'  'N'  'I'  'X'  '\0'

Le caractère '\0' marque la fin de la chaîne. Les fonctions de la bibliothèque peuvent ainsi parcourir les caractères jusqu’à rencontrer ce marqueur.

Cette représentation est simple, mais le programmeur doit prévoir assez de mémoire pour les caractères et pour le marqueur final. Si une fonction écrit au-delà de la zone réservée, elle peut endommager d’autres données. Une partie des erreurs classiques du C vient de cette gestion très directe de la mémoire.

Les structures : décrire les données d’un système

Pour écrire un système d’exploitation, il faut représenter des fichiers, des processus ou des périphériques. Le mot-clé struct permet de regrouper plusieurs données :

struct fichier {
    int numero;
    char nom[14];
};

Une telle structure peut représenter, de manière simplifiée, un numéro de fichier et son nom. Le langage peut ainsi organiser logiquement les informations tout en conservant une représentation concrète et prévisible en mémoire.

Les opérateurs ++ et —

Les opérateurs ++ et -- augmentent ou diminuent une valeur d’une unité :

i++;
j--;

Une explication souvent répétée affirme qu’ils auraient été créés pour exploiter les modes d’adressage avec auto-incrémentation du PDP-11.
Dennis Ritchie indique que cette histoire est impossible : ces opérateurs existaient déjà dans B avant l’arrivée du PDP-11.

Le PDP-7 possédait en revanche quelques cellules mémoire particulières capables de s’incrémenter lors d’un adressage indirect. Elles ont peut-être inspiré Ken Thompson. Ritchie estime également que la forme ++x permettait de produire un code plus compact que x = x + 1 dans l’environnement très contraint de l’époque.

1973 : Unix est réécrit en C

À partir de 1972, le langage et son compilateur deviennent suffisamment efficaces pour envisager une tâche beaucoup plus ambitieuse. Durant l’été 1973, le noyau d’Unix pour le PDP-11 est réécrit en C.

Ce passage constitue un tournant. Un système d’exploitation n’est plus prisonnier de l’assembleur d’une seule machine. La plus grande partie du code peut être conservée, puis recompilée après adaptation du compilateur et de quelques éléments dépendant du matériel.

La portabilité n’est pourtant pas l’objectif principal des toutes premières versions du C. Ritchie explique que cette préoccupation prend davantage d’importance par la suite. Le portage d’Unix vers l’Interdata 8/32, puis vers d’autres architectures, démontre concrètement que le même système peut changer de machine sans être entièrement réécrit.

Les succès d’Unix et du C se renforcent alors mutuellement. Unix fournit au C un environnement réel et une vaste collection d’outils ; le C rend Unix plus lisible, modifiable et transportable.

Un langage volontairement compact

Le C lui-même reste relativement petit. Des fonctions essentielles comme l’affichage, la lecture d’un fichier ou la manipulation d’une chaîne ne sont pas des instructions fondamentales du langage. Elles sont fournies par des bibliothèques.

Le préprocesseur complète également le compilateur. Deux directives deviennent particulièrement importantes :

  • #include insère les déclarations contenues dans un fichier d’en-tête ;
  • #define permet notamment de définir des constantes symboliques ou des macros.

Les premières versions du préprocesseur sont très simples. Il est ensuite enrichi avec des macros comportant des paramètres et avec la compilation conditionnelle.

Cette séparation participe à la philosophie du C : le cœur du langage fournit des mécanismes généraux, tandis que la bibliothèque prend en charge l’interaction avec l’environnement.

Du livre K&R à la norme ANSI

En 1978, Brian W. Kernighan et Dennis M. Ritchie publient The C Programming Language. L’ouvrage présente le langage de manière concise, avec de nombreux exemples.

Pendant plusieurs années, ce livre sert de définition de fait du C. On parle alors couramment de C K&R. Mais le langage continue d’évoluer et les compilateurs ne se comportent pas toujours de manière identique. En 1983, l’ANSI crée donc le comité X3J11. Celui-ci cherche surtout à préciser les pratiques existantes et à renforcer la portabilité sans transformer radicalement le langage.

La norme publiée en 1989, souvent appelée ANSI C ou C89, apporte notamment :

  • des prototypes de fonctions indiquant le type des paramètres ;
  • les qualificatifs const et volatile ;
  • des règles de conversion plus précises ;
  • une description complète du préprocesseur ;
  • une bibliothèque standard commune aux implémentations conformes.

La norme est reprise au niveau international sous la référence ISO/IEC 9899:1990. D’autres révisions suivront, notamment C99, C11, C17 et C23, sans modifier l’identité générale du langage.

Pourquoi le C s’est-il autant diffusé ?

Selon Ritchie, le succès d’Unix constitue le premier moteur de la diffusion du C. Mais cette association n’explique pas tout. Le langage répond aussi à plusieurs besoins durables :

  • efficacité : le compilateur peut produire un code proche des possibilités réelles du processeur ;
  • portabilité : un même programme peut être adapté à différentes architectures sans réécriture complète ;
  • compacité : le langage comporte relativement peu de mécanismes fondamentaux ;
  • contrôle : le programmeur maîtrise la représentation des données et l’utilisation de la mémoire ;
  • stabilité : malgré ses évolutions, le cœur du C reste reconnaissable d’une époque à l’autre.

Le compilateur portable pcc, développé par Stephen C. Johnson, facilite l’adaptation du langage à de nouvelles machines. Dans les années 1980, les compilateurs C deviennent disponibles sur une grande variété d’ordinateurs et de systèmes, y compris les micro-ordinateurs.

Les limites : le prix du contrôle

La proximité avec la machine donne au C sa puissance, mais aussi une grande partie de ses difficultés.

Le langage laisse au programmeur la responsabilité de nombreuses opérations :

  • vérifier qu’un pointeur désigne une adresse valide ;
  • respecter les limites d’un tableau ;
  • réserver puis libérer correctement la mémoire dynamique ;
  • éviter les conversions de types dangereuses ;
  • organiser lui-même les grands programmes en modules cohérents.

Une erreur peut donc dépasser le simple résultat incorrect. Elle peut modifier une zone mémoire voisine, provoquer un arrêt brutal ou créer une vulnérabilité de sécurité.

Ritchie reconnaît également que certaines déclarations sont difficiles à lire et que la relation entre tableaux et pointeurs déroute souvent les débutants. Plusieurs de ces complications viennent de l’histoire du langage : le C a dû évoluer tout en continuant à accepter une grande quantité de code plus ancien.

Un héritage visible dans de nombreux langages

Le C donne naissance à plusieurs descendants directs. Objective-C lui ajoute notamment des mécanismes orientés objet, tandis que C++, développé par Bjarne Stroustrup aux Bell Labs, l’étend profondément.

Son influence se retrouve aussi dans la syntaxe de langages plus éloignés comme Java, C#, JavaScript ou PHP : accolades, points-virgules, opérateurs ++ et --, tests avec if et boucles avec for ou while. Ces langages ne sont pas tous des descendants directs du C, mais ils reprennent une partie de ses conventions.

Le C reste particulièrement adapté aux domaines où la maîtrise des ressources est essentielle : systèmes d’exploitation, pilotes, logiciels embarqués, compilateurs, bibliothèques et programmes devant dialoguer étroitement avec le matériel.

Un langage entre l’assembleur et l’abstraction

La contribution décisive du C consiste à avoir trouvé un équilibre permettant de remplacer une grande partie de l’assembleur sans éloigner complètement le programmeur de la machine. Il montre également qu’un système d’exploitation important peut être écrit presque entièrement dans un langage compilé de haut niveau.

Dennis Ritchie résume lui-même le C comme un langage étrange, imparfait et pourtant extraordinairement réussi. Ses défauts sont inséparables d’un choix fondamental : donner au programmeur des mécanismes simples, généraux et proches de la représentation concrète des données.

L’héritage du C tient donc à trois idées :

  • remplacer l’assembleur pour une grande partie de la programmation système ;
  • rendre le logiciel plus portable d’une architecture à une autre ;
  • conserver un contrôle précis sur la mémoire et sur les ressources de la machine.

Né pour améliorer un environnement de programmation très limité, le C devient ainsi l’un des langages fondamentaux de l’informatique moderne.

Sources principales

 

Publications similaires

  • FLOW-MATIC

    FLOW-MATIC est un langage de programmation développé dans les années 1950 par l’équipe de Grace Hopper chez Remington Rand, puis Sperry Rand UNIVAC. Conçu pour le traitement des données commerciales, il poursuit une idée alors audacieuse : permettre au programmeur de décrire les opérations à effectuer avec des mots proches de l’anglais courant plutôt qu’avec…

  • Fortran

    FORTRAN, contraction de FORmula TRANslation (« traduction de formules »), est l’un des langages les plus importants de l’histoire de l’informatique. Développé chez IBM sous la direction de John Backus, il permet aux scientifiques et aux ingénieurs d’écrire leurs calculs sous une forme beaucoup plus proche des mathématiques que des instructions d’une machine.Lorsque le premier…

  • BCPL

    BCPL signifie Basic Combined Programming Language : une version simplifiée de CPL, conçue pour écrire des compilateurs et des logiciels système plus faciles à transporter d’une machine à l’autre. Au milieu des années 1960, les langages de programmation deviennent plus expressifs, mais leurs compilateurs restent souvent longs et difficiles à adapter. Un langage peut être…

  • ADA

    Ada est un langage de programmation créé à la demande du département de la Défense des États-Unis (DoD) à la fin des années 1970. Son objectif est inhabituel : il ne s’agit pas seulement de faciliter l’écriture des programmes, mais aussi de rendre plus fiables des logiciels complexes destinés à fonctionner pendant de nombreuses années….

  • COBOL

    COBOL, acronyme de Common Business-Oriented Language, est un langage de programmation créé à partir de 1959 pour répondre à un problème devenu crucial : comment écrire des programmes de gestion capables de fonctionner sur des ordinateurs provenant de constructeurs différents ? À la fin des années 1950, IBM, UNIVAC, RCA, Honeywell, Burroughs et d’autres fabricants…

  • Speedcoding

    Speedcoding : le premier langage scientifique moderne oublié de l’histoire de l’informatique En 1953, alors que l’informatique n’en est encore qu’à ses débuts, IBM introduit un système de programmation qui va profondément transformer la manière d’utiliser les ordinateurs scientifiques : Speedcoding. Conçu sous la direction de John W. Backus, ce système ne cherche pas d’abord…