Aller au contenu principal

Programmation C · L2 · Section 1/12

Introduction au C

Progression

Points d’expérience : XPSérie de jours consécutifs : · —Progression du module : — / —compris

#Introduction au langage C

Le premier contact avec C passe par l’outillage : un éditeur de texte, un compilateur (gcc ou clang), un terminal. Contrairement aux langages gérés qui embarquent un runtime, C produit un exécutable natif qui parle directement au système d’exploitation : pas de collecteur embarqué, pas de vérification à l’exécution. Le contrat est simple et exigeant : ce que vous écrivez est exécuté tel quel, y compris vos erreurs. Comprendre la chaîne de compilation donne de l’assurance pour la suite du module.

#1. Anatomie d’un programme minimal

cc

1#include <stdio.h>2 3int main(void) {4    puts("Hello, C !");5    return 0;6}

Quatre éléments structurent ces lignes :

  • #include <stdio.h> insère les déclarations de la bibliothèque d’entrées/sorties pendant le prétraitement ; sans elle, l’appel à puts serait refusé (déclaration implicite interdite par les compilateurs actuels).
  • int main(void) est la signature définie par la norme pour un programme sans arguments : elle renvoie un int et ne prend rien. Ce n’est pas cosmétique, c’est le contrat avec le système.
  • puts écrit la chaîne sur la sortie standard suivie d’un saut de ligne. Préférez puts à printf quand il n’y a rien à formater : sans chaîne de format, pas de bug de format, et printf(user_input) serait de toute façon une vulnérabilité.
  • return 0; signale la réussite au système. Omettre ce return dans main reste valide : la norme garantit un retour implicite de 0.

Compilation et exécution :

shsh

1cc -std=c17 -Wall -Wextra -Wpedantic hello.c -o hello2./hello        # affiche : Hello, C !3echo $?        # affiche : 0

#2. Les quatre phases de la compilation

La commande précédente enchaîne quatre étapes que l’on peut observer séparément :

shsh

1cc -E hello.c -o hello.i   # 1. prétraitement : directives résolues2cc -S hello.i -o hello.s   # 2. compilation : C vers assembleur3cc -c hello.s -o hello.o   # 3. assemblage : code objet réadressable4cc hello.o -o hello        # 4. édition de liens : symboles résolus
  • Prétraitement : #include recopie le contenu des en-têtes, #define effectue des substitutions textuelles. hello.i contient des milliers de lignes car stdio.h s’y déroule entièrement.
  • Compilation : le compilateur traduit le C en assembleur et émet les diagnostics. C’est la phase où les warnings sauvent des heures de débogage.
  • Assemblage : l’assembleur produit un fichier objet, du code machine avec des références externes non résolues (puts n’existe pas encore dans le binaire).
  • Édition de liens : le linker (ld, piloté par le driver cc) résout puts dans la bibliothèque C et produit l’exécutable.

Cette gymnastique vous servira lors du débogage : un symbole indéfini est un problème d’édition de liens, pas de syntaxe, et les messages des deux phases se lisent différemment. Elle prépare aussi les builds séparés du chapitre sur la modularité.

#3. Une discipline de compilation dès le premier jour

Adoptez immédiatement des options strictes ; placez-les dans un Makefile pour ne pas compter sur votre mémoire :

  • -std=c17 fixe le dialecte du langage (gnu17 y ajoute des extensions GNU ; réservez-les aux projets qui les assument).
  • -Wall -Wextra -Wpedantic activent les diagnostics les plus rentables : variable non initialisée, comparaison signé/non signé, conversions suspectes.
  • -Wconversion signale les conversions implicites rétrécissantes, précieuse avant tout protocole binaire.
  • -g embarque les informations de débogage ; sans elle, ASan et Valgrind ne parlent qu’en adresses brutes.
  • -fsanitize=address,undefined instrumente le binaire pour détecter au runtime dépassements de tampon, use-after-free et comportements indéfinis arithmétiques. À réserver aux builds de test : le binaire est plus gros et plus lent.

#4. Parler au shell : arguments et code de sortie

cc

1#include <stdio.h>2 3int main(int argc, char **argv) {4    printf("programme : %s\n", argv[0]);5    for (int i = 1; i < argc; ++i) {6        printf("argument %d : %s\n", i, argv[i]);7    }8    return 0;9}

La norme garantit : argc vaut au moins 1, argv[0] est le nom du programme, argv[argc] vaut NULL, et les chaînes sont modifiables. Leur durée de vie est celle du programme : ne libérez jamais argv[i], et copiez toute chaîne que vous souhaitez conserver au-delà de main.

shsh

1./args un deux2# programme : ./args3# argument 1 : un4# argument 2 : deux

Le code de sortie de main devient le statut du processus : 0 pour succès, non nul sinon. C’est la valeur lue par $?, testée par les scripts shell et les Makefiles. Restez entre 0 et 255 : le shell tronque au-delà.

#Atelier

  1. Compilez le programme minimal avec gcc puis avec clang (mêmes options), comparez : ls -l hello-gcc hello-clang.
  2. Activez un sanitizer et provoquez un dépassement volontaire :
cc

1#include <string.h>2 3int main(void) {4    char tampon[8];5    strcpy(tampon, "bien trop long pour huit octets");6    return (int)tampon[0];   /* force la lecture du tampon */7}
shsh

1cc -std=c17 -g -Wall -Wextra -fsanitize=address,undefined debord.c -o debord2./debord
  1. Générez l’assembleur avec cc -S hello.c et repérez l’appel à puts.
Sorties attendues et lecture du rapport ASan

1. Les tailles diffèrent de quelques pourcents entre GCC et Clang : chaque compilateur embarque son propre prologue et son runtime. 2. ASan interrompt le programme avec une première ligne du type ERROR: AddressSanitizer: stack-buffer-overflow, suivie de la pile d’appels (numéros de ligne exacts grâce à -g) et d’un SUMMARY: AddressSanitizer: stack-buffer-overflow .... GCC a de son côté émis un warning sur ce strcpy : les deux niveaux de détection sont complémentaires. 3. Dans hello.s (syntaxe AT&T sous Linux), cherchez la ligne call puts ou callq puts@PLT : c’est l’appel résolu au chargement.

Quelle option arrête la chaîne juste après la production du fichier objet ?
Quelle option arrête la chaîne juste après la production du fichier objet ?