Version sans JavaScript, en lecture seule : les ateliers, les quiz et l’exécution du code n’y sont pas. Version complète · Catalogue · Se connecter

EPITA · Toulouse · 2e année (cycle préparatoire)

Programmation en C

Le C et son environnement Unix : compiler, pointeurs et tableaux, organisation de la mémoire, structures de données, entrées-sorties, processus, et la programmation concurrente avec les threads POSIX.

cours et feuilles d'exercices | C · POSIX

Le cours de programmation en C de deuxième année, à l'EPITA Toulouse, en 2019-2020.

Huit chapitres, un supplément et sept feuilles d'exercices. Les chapitres 0 et 1 situent le C et montrent comment un fichier source devient un programme ; les chapitres 2 à 4 sont le cœur du langage : pointeurs, mémoire, structures de données. Le supplément qui suit le chapitre 2, « Le tableau de la mémoire », est ma façon d'expliquer les variables et les pointeurs à qui n'a pas compris le C : il manque, je trouve, à tous les cours de C. Les chapitres 5 à 7 parlent au système : fichiers et tubes, processus, puis threads.

Les exemples compilent sans avertissement avec gcc -std=c11 -Wall -Wextra -Werror sous Linux, sauf ceux qui sont là pour montrer un avertissement ou une erreur ; ceux qui touchent au système demandent aussi -D_DEFAULT_SOURCE ou -pthread. C'est indiqué à chaque fois. Tapez-les, compilez-les, modifiez-les : un programme C ne se comprend qu'en le faisant tourner, et en le faisant planter.

Supports de cours

  1. Introduction : Pourquoi le C, ce que veut dire « compilé » et « bas niveau », et les quelques règles d'écriture qu'on tiendra tout le semestre.
  2. Compiler et exécuter : Du fichier source au programme qui tourne - préprocesseur, compilateur, assembleur, éditeur de liens et chargeur. Les sections de l'exécutable (text, rodata, data, bss) et leurs droits. Les options de gcc, les programmes en plusieurs fichiers, les fichiers d'en-tête et make.
  3. Pointeurs et tableaux : Une adresse est une valeur comme une autre. Les opérateurs & et *, le passage par adresse, void *, les tableaux et ce qui les distingue des pointeurs, l'arithmétique des pointeurs, les chaînes et const.
  4. Le tableau de la mémoire : Suivre des pointeurs à la main sans dessiner de flèches - une ligne par variable, avec son adresse, son contenu, son nom, son type et sa taille. Un pointeur de pointeur, un tableau, le passage par adresse et une chaîne, lus dans le tableau. Puis pourquoi seule la taille compte, le type n'étant qu'une illusion, et les tailles à connaître selon les machines.
  5. La mémoire d'un programme : Où vivent les données - zone statique, pile et tas. Les constantes et les initialiseurs, la durée de vie des variables locales, malloc et free, la mémoire virtuelle, brk et mmap, les pools et les arènes. Et deux outils pour trouver les erreurs de mémoire.
  6. Structures de données : Les structures et typedef, les types abstraits, et les classiques de l'algorithmique écrits en C - listes chaînées, files, arbres binaires, vecteurs. Passer un pointeur par valeur ou par adresse, et le lire dans la signature.
  7. Entrées-sorties : Sous Unix, tout est fichier - les descripteurs, read et write, ouvrir et fermer, se déplacer, rediriger, les tubes. Puis les tampons de stdio.h, et un fichier vu comme de la mémoire avec mmap.
  8. Processus : Lancer des programmes depuis son code, comme le fait un shell - fork pour dupliquer un processus, wait pour attendre sa fin, exec pour lui faire exécuter un autre programme. Orphelins et zombies, redirections et tubes entre processus, et les signaux.
  9. Parallélisme : Plusieurs fils d'exécution qui partagent la même mémoire - pourquoi un simple compteur devient faux, comment on protège une section critique (Peterson, verrous, conditions, sémaphores), le problème des producteurs et des consommateurs, et la programmation avec les threads POSIX.

Feuilles d'exercices

  1. Compiler et exécuter : Reconnaître quelle étape échoue, lire ce que fait le préprocesseur, bien découper un programme en modules, et écrire un Makefile. Chapitre 1.
  2. Pointeurs et tableaux : Suivre des pointeurs à la main, prévoir des sizeof, trouver des bogues classiques, puis écrire avec des adresses - minmax, chaînes, matrices. Chapitre 2.
  3. La mémoire d'un programme : Situer chaque donnée dans sa zone, déjouer les macros, trouver fuites et débordements, puis écrire avec malloc - concaténation, matrices, et un pool de recyclage. Chapitre 3.
  4. Structures de données : Lire des signatures, écrire des fonctions sur les listes avec un pointeur sur pointeur, trouver les bogues d'un module, une pile et un arbre binaire de recherche en types abstraits. Chapitre 4.
  5. Entrées-sorties : Prévoir ce qu'écrit un programme selon que sa sortie va au terminal ou dans un fichier, écrire wc, tee et tail avec read, write et lseek, suivre les positions partagées, faire passer des entiers dans un tube. Chapitre 5.
  6. Processus : Compter les processus créés par fork, comprendre ce que copie fork, écrire system et un petit shell, récupérer ses zombies, compter des signaux. Chapitre 6.
  7. Parallélisme : Entrelacements et mises à jour perdues, trouver et corriger un interblocage, déboguer une file bornée, passer des arguments aux fils d'exécution, un rendez-vous avec des sémaphores, et le coût d'un verrou. Chapitre 7.