Pourquoi choisir le langage C ?

Le langage C occupe une place centrale dans l’informatique moderne. Il est utilisé lorsqu’on a besoin de travailler au plus près du matériel, ou pour la performance : systèmes d’exploitation, pilotes, logiciels embarqués, ou encore moteurs d’exécution de nombreux langages, moteur de jeux vidéo, sécurité. C’est pour cette raison que des systèmes comme Unix, Windows, Mac OS X ou GNU/Linux, ainsi que les interpréteurs de Python, Ruby, PHP, Java ou Perl, sont écrits en C ou c++. Même votre shell repose sur du code C, openssl également, etc.

Apprendre le C, c’est remonter à la source de nombreux langages actuels. Il a influencé une grande partie des technologies de programmation contemporaines. Malgré une syntaxe relativement simple, il expose directement les concepts fondamentaux qui régissent le fonctionnement d’un processeur et sont système hôte. Le programmeur accède ainsi à un contrôle fin des ressources, ce qui explique pourquoi le C produit un code presque aussi efficace que l’assembleur, tout en restant beaucoup plus lisible et “portable”. Grâce à des compilateurs comme GCC ou Clang, un programme écrit en C peut être adapté à pratiquement n’importe quelle architecture matérielle.

Bien qu’il ne propose pas naturellement les abstractions avancées des langages de haut niveau, il permet de les construire. Des environnements complexes comme GTK pour Gnome ou la bibliothèque OpenGL sont entièrement réalisés en C. De nombreux langages majeurs : C++, C#, Objective-C, Java, tirent leurs bases du C. Maîtriser le C, c’est comprendre les principes fondamentaux qui gouvernent l’exécution de tout programme : gestion de la mémoire, organisation du processeur, coût réel des opérations. Cela permet notamment d’évaluer plus facilement la complexité d’un algorithme, car la plupart des instructions en C s’exécutent en temps constant (Attention, elles n’ont pas le même cout).

C la version espion :

Texas Instruments a présenté le MSPM0C1104, qu’il décrit comme le plus petit microcontrôleur au monde.Mesurant seulement 1,38 mm² et comprend un cœur Arm Cortex-M0+ à 24 MHz, 16 Ko de mémoire flash, 1 Ko de SRAM, un convertisseur analogique-numérique 12 bits à trois canaux et six broches GPIO.

Aujourd’hui, vous pouvez intégrer un ORDINATEUR COMPLET dans un simple fil. Détection analogique, LED, communications Bluetooth, traitement, mémoire numérique : tout y est

Il n’existe pas d’alternative a C (ou asm) pour le moment. Rust produit souvent des binnaire plus volumineux et ne peux être embarqué pour le moment.

Historique

Le langage C a été conçu progressivement au début des années 1970 par Ken Thompson et Dennis Ritchie, à partir du langage B, dans le but de rendre Unix plus facilement portable (et plus facile a écrire que de l’assembleur). Cette première version est ce que l’on appelle le C originel, ou K&R. Très rapidement tout le monde veux sont propre language C, avec ces extensions spécifique, resultant en dinombrable compilateur maison et normes différentes (IBM, Microsoft, …).

Dennis Ritchie

  • 1969 - 1973 - Conception à Bell Labs
  • 1978 - “The C Programming Language” book by Ritchie & Kernighan
  • plusieurs standard, extensions, etc
  • C with class 1982 (AT&T Bell Labs)
  • 1983 - ANSI committee formed to standardize C
  • 1989 - ANSI C standard (C89) released
  • 1990 à 2024 -> C90/C99/C11/C17/C23

En 1989, le langage est standardisé et devient l’ANSI C, ce qui permet d’unifier la syntaxe et de corriger certaines particularités du C initial. Cette version est encore très répandue dans le monde de l’embarqué. Aujourd’hui, la norme la plus utilisé est le C99, largement compatible avec les versions précédentes, mais enrichie de nouvelles fonctionnalités. Le language est toujours actif et continue d’évoluer. La norme courante est C23, moins compatible, certain mot-clés ont changé de signification telque register. Mais C23 ou Fill-C reste compétitif.

Le C influence aussi de nombreux language dont sont “sucesseur”le plus direct, si l’on peu dire : le C++ écris initalement par Bjarne Straustroup qui travaillais aussi à Bell Labs mais également à AT&T. Attention C++ est en language multi-paradigme il permet la POO mais n’est pas considéré comme un language OO. Sont point fort c’est avant tout la meta programmation. Et notez qu’il exite le C with class en 1982.

Bjarne Straustroup

  • 1985 (The C++ Programming Language)
  • 1998 (ISO C++98)
  • 2023 (ISO C++23)

Aujourd’hui C et C++ suivent un développement parallele. Comprendre le C et les méchansimes soujacent
permet de maitriser plus rapidement tout les languages qu’il a influencé et les méchanisme bas niveaux.

Caractéristiques importantes

Le langage C se distingue par plusieurs éléments fondamentaux :

  • il s’agit d’un langage compilé : le code est directement transformé en instruction machine compatible avec le système hôte. Pas d’interpreteur, pas de perte de temps.
  • les variables ont une portée limitée à leur bloc
  • il repose sur un modèle impératif : calculs, conditions, boucles
  • il encourage la programmation structurée grâce aux fonctions, “modules” et appels récursifs
  • il permet de construire des structures de données complexes via l’agrégation, les tableaux ou les unions
  • le typage est partiellement faible à la compilation (fondamentalement ont manipule des données brutes)
  • l’accès mémoire se fait au moyen de pointeurs (ce sont des adresses i.e une position dans la mémoire)
  • une partie de la gestion mémoire reste sous la responsabilité du programmeur (allocation dynamique)
  • les pointeurs de fonction offrent une forme de polymorphisme rudimentaire
  • le préprocesseur facilite la compilation séparée (code spécifique a certaines plateformes) et réduit les temps de compilation.
  • la bibliothèque standard (libc) fournit les outils essentiels : chaînes, entrées/sorties, fonctions mathématiques
  • la syntaxe est concise et expressive : nombreux opérateurs, blocs délimités par accolades, instructions terminées par un point-virgule.

Ce qui manque au C

Certaines fonctionnalités présentes dans des langages plus modernes ne sont pas intégrées au C :

  • pas d’assignation directe de tableaux (même si les structures peuvent être assignées) ;
  • pas de gestion automatique de la mémoire par défaut (mais cela reste possible via des reference counter ou garbage collector)
  • aucun contrôle sur les dépassements d’indice dans les tableaux ou sur les adresses.
  • pas de mécanisme d’exceptions (d’autant plus que c’est extremement long donc une mauvaise pratique en prog système)
  • pas de surcharge des opérateurs ou des fonctions (sauf sur les nouvelles normes C)
  • aucun support natif pour la programmation orientée objet (héritage, polymorphisme) (mais ont peut s’ens sortir via les pointeur de fonctions pour mimer le comportement objet)
  • pas de bibliothèque standard intégrée pour le multithreading, le réseau ou l’affichage graphique (seule la console est supportée). et n’est pas si standard (n’existe pas en embarqué)

Pour pallier ces manques, la norme POSIX définit des bibliothèques standardisées, comme libpthread pour le multithreading. De leur côté, les systèmes Unix, Linux ou Windows proposent leurs propres bibliothèques, avec des niveaux de portabilité variables.

Compilation d’un programme C

Un programme C s’écrit dans un fichier texte, typiquement avec l’extension .c. Ce code source est transformé en programme exécutable par un compilateur adapté à l’architecture et au système d’exploitation ciblés : parmi les plus courants, gcc, clang, icc ou Visual Studio. La compilation ne consiste pas seulement à transformer le texte en binaire : elle implique plusieurs étapes que l’on peut observer via des fichiers intermédiaires.

Étapes détaillées de la compilation :

  1. Prétraitement Le préprocesseur “déplie” les directives (macros, inclusions) et produit un fichier .i contenant le code C avec tous les #include et #define développés -> i.e elle ont été sppliquées
  2. Compilation Le compilateur analyse d’abord la syntaxe du code et construit une représentation interne : l’arbre syntaxique abstrait (AST). C’est une forme structurée qui permet de vérifier la validité du code et d’optimiser sa traduction. À partir de l’AST, le compilateur génère le code assembleur (.s) pour l’architecture cible.
  3. Assemblage Le code assembleur .s est converti en code binaire objet (.o), encapsulant les instructions machines dans un format compressé, mais décodable via des outils spécialisés.
  4. Édition des liens (linking) Le linker regroupe un ou plusieurs fichiers objets ainsi que les bibliothèques systèmes et externes pour produire l’exécutable final pouvant être lancé sur votre système.

Pour observer chaque étape :

gcc -Wall -save-temps -c hello.c

génère :

  • hello.i : code source après prétraitement (plus de directive #)
  • hello.s : code assembleur issu de l’AST (directment lisible)
  • hello.o : fichier objet binaire (pas trop lisible)

L’option -Wall (“Warnings All”) active l’affichage de la plupart des avertissements du compilateur. Son but est de détecter les erreurs potentielles et les mauvaises pratiques directement lors du processus de compilation : cela aide à garantir un code plus fiable et conforme aux standards.

En lançant la commande :

gcc -Wall -c hello.c

le compilateur ne produit un fichier .o qu’en l’absence de blocage critique et rend la main directement si aucune erreur n’est détectée.

Après la compilation, le fichier objet hello.o contient uniquement le code machine correspondant à votre source C. Il ne peut pas être exécuté seul : il ne contient pas les implémentations des fonctions externes que vous invoquez (comme printf dans la bibliothèque standard). Il faut donc effectuer l’édition des liens, qui associe vos appels à leurs véritables définitions dans les bibliothèques du système pour créer l’exécutable.

gcc hello.o -o hello

À cette étape, le linker recherche automatiquement la bibliothèque standard avec toutes les fonctions usuelles. Si votre programme utilise des fonctions externes à la bibliothèque standard, il faut spécifier les bibliothèques concernées lors de l’édition des liens :

  • Pour les fonctions mathématiques (sin, cos, sqrt, …), la bibliothèque libm n’est pas liée automatiquement. Il faut alors ajouter le flag : gcc my_program.o -o my_program -lm
  • Pour utiliser les fonctionnalités de threads POSIX (pthread_create, …), il faut spécifier gcc my_program.o -o my_program -lpthread

Exemple complet avec la bibliothèque mathématique :

#include <stdio.h>
#include <math.h>
int main(void) {
   printf("pi=%f\n", M_PI);
   return 0;
}

Pour compiler et lier correctement :

gcc -Wall -c pi.c
gcc pi.o -o pi -lm

L’examen du fichier hello.o produit révèle du code machine binaire, souvent représenté sous forme hexadécimale :

7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
02 00 3e 00 01 00 00 00 50 10 40 00 00 00 00 00

Ce code est le résultat compressé de l’assembleur issu de l’AST : il peut être désassemblé pour retrouver le code initial (ou presque), bien que le contenu ne soit pas conçu pour être lu ou compris facilement par un humain. C’est le domaine du reverse engineering

Chaque option et étape du compilateur GCC a un rôle précis dans la chaîne : activant la rigueur (-Wall), contrôlant l’arrêt du processus (-c, -E, -S), ou précisant les bibliothèques à lier (-lm, -lpthread), elles permettent un contrôle fin de la fabrication d’un exécutable fiable et optimisé pour la plateforme visée.