Table des matières
TLDR
- Avant de commencer le code
- Établir une arborescence de fichiers cohérente au projet
- Utiliser l'outil de versionnage Git
- Configurer VSCode/VSCodium
- Quand on code
- Faire un Makefile propre et structuré
- Commenter son code
- Réaliser des tests
- Réaliser un
README.mdcohérent et structuré - Réaliser son rapport de projet en LaTeX
- Autre
- Préparer l'archive du projet
- Spécificité macOS : avoir recours à la conteneurisation (facultatif)
Préambule
Vous êtes étudiant(e) à l'ensIIE en première année, et on vous annonce que vous devez faire un projet de C. On vous dit que vous devez commenter votre code, faire des tests, réaliser un manuel d'utilisation, et le pire de tout : faire un rapport !
Petit détail : vous n'avez (peut-être) jamais fait de projet où vous devez coder. Par quoi devez-vous commencer ? Que devez-vous faire ? Qu'est-ce qui est attendu ?
Il n'existe pas d'unique réponse sur le comment faire un projet. Mais comme cela reste (peut-être) votre première fois, on va essayer de tracer une ligne directive pour vous aider. Cela vous permettra de vous travailler dans de bonnes conditions tout en vous assurant que vous n'aurez rien oublié.
À destination des iien(ne)s : nous nous appuierons sur le projet de C fait au premier semestre en première année. Cela vous permettra d'avoir un repère.
On prépare le terrain
Arborescence de fichiers
Comment structurer votre projet ?
Supposons que votre projet est dans un dossier nommé prim-projet. On appellera ce dossier la racine du projet.
À la racine du projet, nous retrouvons essentiellement les éléments suivants :
- un fichier
README.md(contenu simple et précisé ci-après), - un dossier
code(qui contiendra le code associé au projet), - un dossier
rapport(qui contiendra vos fichiers pour réaliser votre rapport et son PDF ; nous développerons là aussi comment réaliser le rapport ci-après), - un dossier
soutenance(qui contiendra vos fichiers pour réaliser votre rapport et son PDF si vous devez réaliser une présentation du projet).
Le fichier README.md présent à la racine introduira seulement le contenu de l'archive que vous enverrez. Cela vous permet de faire le point sur ce qu'est censé contenir votre archive.
Voici un exemple de contenu minimaliste de ce fichier :
# PRIM11 - Projet
[PLACEHOLDER] Une phrase d'introduction du projet (pour la forme).
## Rapport
Voir le fichier [**`rapport/main.pdf`**](rapport/main.pdf).
## Soutenance
Voir le fichier [**`soutenance/main.pdf`**](soutenance/main.pdf).
## Fonctionnement du code source
Voir le fichier [**`code/README.md`**](code/README.md).
## Auteurs
- Prénom1 NOM1 (`prenom1.nom1@ensiie.eu`)
- Prénom2 NOM2 (`prenom2.nom2@ensiie.eu`)
À présent, attardons-nous sur le dossier code.
D'autre part, dans le cadre du projet de C, le dossier code peut contenir les fichiers suivants :
- un fichier
README.md| Ce fichier sera notre manuel d'utilisation du projet. Nous préciserons davantage son contenu ci-après. - un fichier
Makefile| Ce fichier permettra d'orchestrer la compilation et l'exécution du projet. Nous préciserons davantage son contenu ci-après. - un dossier
bin| bin est l'abréviation de binary. Ce dossier contient notre/nos exécutable(s). - un dossier
obj| obj est l'abréviation de object. Ce dossier contient tous les fichiers au format*.o. - un dossier
src| src est l'abréviation de source. Ce dossier contient le cœur du code source, c'est-à-dire les fichiers*.het les fichiers*.c.
Si vous avez beaucoup de fichiers pour le code source, vous pouvez aussi mettre vos fichiers *.h dans un dossier inc au lieu de les mettre dans le dossier src. Dans le cadre du projet de C, cela ne semble pas délirant de tout mettre ensemble.
Si votre code est destiné à générer des fichiers en sortie, cela peut être intéressant que ces derniers soient séparés du code. Le dossier out est fait pour cela.
Cela n'est pas forcément nécessaire dans le cadre du projet de C, mais vous pouvez aussi réaliser ce qu'on appelle des tests unitaires pour vérifier des morceaux de votre code. Nous développerons un exemple d'application intéressant ci-après.
Pour d'autres projets, vous pouvez retrouver ajouter les dossiers suivants si leur présence est justifiée :
- un dossier
doc| Utilisé pour générer une page web ou un PDF LaTeX de la documentation de votre code. Cela est notamment abordé en cours d'INPS (introduction à la programmation scientifique) au premier semestre de deuxième année, donc je ne développerai pas comment réalisé cela. - un dossier
lib| Utilisé si vous générez des bibliothèques à partir de votre code source. Cela est notamment abordé en cours de COAV (compilation avancée) au premier semestre de troisième année, donc je ne développerai pas comment réalisé cela. - un dossier
misc| Utilisé pour ajouter des fichiers utiles pour le code mais sans plus. Je recommande d'utiliser ce dossier si vous avez des fichiers d'entrée dans un format particulier ou bien pour un projet personnel, mais à éviter pour un projet à rendre (cela peut donner l'impression que vous ne savez pas vous organiser).
Somme toute, voici un exemple d'arborescence d'un projet en appliquant les remarques précédentes :
prim-projet
├── README.md
├── code
│ ├── Makefile
│ ├── README.md
│ ├── bin
│ ├── obj
│ ├── src
│ │ ├── feature.c
│ │ ├── feature.h
│ │ ├── main.c
│ │ ├── Makefile
│ │ ├── stack.c
│ │ └── stack.h
│ └── tests
│ ├── Makefile
│ └── test_stack.c
└── rapport
├── main.pdf
└── main.tex
Git
J'ai entendu dire qu'on allait découvrir Git au deuxième semestre de première année, ça me sert à rien d'apprendre à l'utiliser maintenant, non ?
Réponse simple : si, c'est utile.
Avant tout, rappelons ce qu'est Git : Git est un logiciel de gestion de versions décentralisé. Les avantages de l'utiliser sont nombreux.
Je ne souhaite pas faire de tutoriel pour apprendre à utiliser. De plus, comme vous faites le projet de C seul(e), vous n'aurez pas besoin de partager le code de votre projet avec d'autres personnes. Ici, l'intérêt principal d'utiliser Git est pour vous permettre d'utiliser Gitlab et, en particulier, le Gitlab de l'école (disponible à l'adresse https://gitlab.pedago.ensiie.fr). Allez sur le site directement pour voir comment créer un dépôt.
Figurez-vous que j'ai déjà entendu des personnes perdre un projet pendant des vacances car leur ordinateur les a lâché (fâcheux). Si vous utilisez le dépôt Gitlab de l'école, vous aurez l'occasion de déposer plusieurs versions de votre projet sur un serveur distant et alors être en mesure de le récupérer sur n'importe quel appareil. Je ne peux alors que vous recommander d'utiliser Git pour votre projet pour au moins avoir une backup de votre projet.
Il existe de nombreux tutoriels pour vous introduire à Git. Je vous laisse le loisir de les parcourir.
Toutefois, je souhaite mettre l'accent sur deux fichiers qui vous seront utiles : le fichier .gitignore et le fichier .gitkeep.
Le fichier .gitignore, situé à la racine du projet, permet de lister les fichiers que vous ne voulez pas suivre avec l'outil Git. Typiquement, les fichiers objets *.o et l'exécutable ne sont pas à commit.
Un exemple de fichier .gitignore minimaliste :
# Generated files
*.o
a.out
# Mac files
.DS_Store
D'autre part, un fichier .gitkeep est un fichier utilisé par les développeurs pour créer un dossier vide dans un dépôt Git. En effet, on ne peut pas ajouter un dossier vide dans un dépôt Git, donc l'usage est de créer ce fichier pour pouvoir suivre le dossier par défaut. Je vous invite à créer ce fichier vide dans les dossiers bin, obj, ainsi que out et lib si vous avez de tels dossiers.
VSCode/VSCodium
Si vous ne savez pas sur quelle éditeur réaliser votre projet, je ne peux que vous recommander VSCode (développé par Microsoft, open source) ou VSCodium (éditeur de code reprenant le code source de VSCode mais est essentiellement développé par des utilisateurs indépendants).
Extensions
L'une des raisons principales justifant l'utilisation de VSCode/VSCodium est l'utilisation des extensions. Ces dernières offrent plus de confort pour développer en proposant une interface simple et propre tout en ajoutant des raccourcis ou de la coloration syntaxique, par exemple.
Voici ma suggestion d'extensions pour le projet de C sur VSCodium :
- clangd | Ajout de la coloration syntaxique en C/C++.
- Clang-Tidy | Complément de l'extension clangd.
- Doxygen Documentation Generator | Générateur de documentation Doxygen. Vous comprendrez l'intérêt de cette extension ci-après.
- Todo Tree | Ajout de surbrillance de certains mots-clés comme
TODO,FIXME, ou autre. Utile pour le développeur. - LaTeX Workshop | Ajout de fonctionnalités pour réaliser un rapport fait en LaTeX sur le dépôt. Si VSCodium vous suggère d'installer des extensions en plus, consultez la page de cette dernière pour vérifier si elle peut vous être utile.
Configuration des extensions
J'en ai déjà pas mal parlé dans d'autres posts. De ce fait, je vous invite directement à regarder ce post pour paramétrer votre environnement VSCode/VSCodium. Je vous redirige aussi vers ce post si votre projet est en Python.
Cette partie n'est clairement pas obligatoire, mais cela reste un confort supplémentaire.
On commence le projet !
Une fois que vous avez bien préparé votre environnement de travail, il est temps de passer au boulot !
On reste motivé, et gardons un œil sur les éléments qui vont vous être présentés.
Makefile
Assez souvent négligé, le réaliser proprement dès le début est pourtant un gain de temps considérable.
Il existe deux écoles :
- ceux qui préfèrent avoir un seul Makefile dans le dossier
codequi fait tout, - ceux qui préfèrent avoir un Makefile dans le dossier
codeet des sous-Makefile dans les dossierssrc,tests, et autre.
Je vais alors vous présenter les deux approches, je vous laisserai le soin de faire ce que vous préférez.
Unique Makefile
On crée un Makefile dans le dossier code. Celui-ci coordinera l'ensemble de nos compilations/exécutions. Considérez alors l'exemple suivant :
# Makefile inspiré de ce site : https://dev.to/medunes/build-c-projects-like-a-pro-a-guide-to-idiomatic-makefiles-53b6
# ---- configuration ---- #
SRC_DIR := src
INC_DIR := include
TEST_DIR := tests
OBJ_DIR := obj
BIN_DIR := bin
BIN := $(BIN_DIR)/main
SRCS := $(wildcard $(SRC_DIR)/*.c)
# OBJS := $(SRCS:.c=.o) # pour avoir les fichiers objets dans le dossier src
OBJS := $(patsubst $(SRC_DIR)/%.c,$(OBJ_DIR)/%.o,$(SRCS))
TEST_SRCS := $(wildcard $(TEST_DIR)/*.c)
# TEST_OBJS := $(TEST_SRCS:.c=.o) # pour avoir les fichiers objets dans le dossier tests
TEST_OBJS := $(patsubst $(TEST_DIR)/%.c,$(OBJ_DIR)/%.o,$(TEST_SRCS))
CC := gcc
CFLAGS := -Wall -Wextra -I$(INC_DIR)
LDLIBS :=
# ---- build targets ---- #
$(BIN): $(OBJS)
$(CC) $^ -o $@ $(LDLIBS)
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
$(CC) $(CFLAGS) -c $< -o $@
$(OBJ_DIR)/%.o: $(TEST_DIR)/%.c
$(CC) $(CFLAGS) -c $< -o $@
.PHONY: test test-bin clean
test-bin: $(TEST_OBJS) $(OBJS)
$(CC) $(TEST_OBJS) $(filter-out $(OBJ_DIR)/main.o,$(OBJS)) -o $(BIN_DIR)/test_runner
test: test-bin
$(TEST_DIR)/run.sh
clean:
rm -f $(OBJS) $(TEST_OBJS) $(BIN) $(BIN_DIR)/test_runner
Actuellement, ce Makefile ne prend pas en compte de potentielles modifications des fichiers .h. De ce fait, si vous modifiez ces derniers, faites un make clean pour vous assurer de n'avoir aucun souci (ou ajoutez des dépendances des fichiers .h dans le Makefile).
À titre personnel, je n'aime pas avoir un dossier obj. Je trouve qu'il est raisonnable d'avoir tous les fichiers objets dans le dossier src tant que les exécutables finaux sont séparés dans un autre dossier bin. Cela permet d'ailleurs de faciliter l'organisation du Makefile au niveau des dépendances.
Plusieurs Makefile
L'idée ici est qu'une instrution donnée au Makefile principal sera transmise aux sous-Makefile. En guise d'exemple, voici un Makefile présent dans le dossier code :
.PHONY: all tests doc clean
all:
$(MAKE) -C src all
tests: all
$(MAKE) -C tests all
doc:
$(MAKE) -C doc all
clean:
$(MAKE) -C src clean
$(MAKE) -C tests clean
$(MAKE) -C doc clean
Ainsi, quand on tape make dans le terminal, cela réalise les instructions associées à la cible all, donc cela exécute $(MAKE) -C src all. Cette ligne de commande signifie qu'on exécute la commande make all en se plaçant dans le dossier src. Le Makefile src/Makefile ressemblerait alors à celui donné ci-dessus tout en faisant attention au niveau des chemins relatifs.
Documentation
Systématiquement, on vous demande de commenter/documenter votre code. Cela est nécessaire pour que le développeur (vous) puissiez établir ce que votre code est censé faire, et aussi pour votre correcteur de constater que vous comprenez ce que vous faites. Prenez l'habitude de documenter votre code !
Comment documenter ?
Avant tout, un détail tout bête : mettez-vous d'accord si vous faites des commentaires en anglais ou en français. Évitez de mélanger les deux langues.
Recommandation personnelle
La recommandation est à appliquer si on ne vous impose aucun moyen particulier pour commenter votre code.
Dans le cadre du projet de C, bien qu'on vous ne l'introduit pas en première année, je vous invite fortement à établir la convention de Doxygen. Doxygen est un générateur de documentation sous licence libre capable de produire une documentation logicielle à partir du code source d'un programme. C'est pour cela que je vous ai invité à télécharger l'extension Doxygen Documentation Generator : pour vous faciliter la génération de la documentation !
Ce qui peut être attendu dans les projets
C'est sympa tout ça, mais dans la consigne, on veut que le code commenté contienne
@requires,@assignset@ensures. Pourquoi ? Ça vient d'où ?
Ici, on vous invite plutôt à employer le standard ASCL. Ce standard est intéressant dans le domaine de la vérification formelle.
Vous pouvez alors consulter le site suivant pour avoir un exemple de documentation avec ce standard : https://frama-c.com/html/acsl.html. Vous pouvez l'adopter pour le projet de C pour satisfaire le correcteur.
Que documenter ?
En clair, nous essaierons de documenter toutes les fonctions en précisant succinctement au minimum les paramètres, ce que la fonction fait, et ce que la fonction renvoie (si elle renvoie quelque chose).
D'autre part, c'est plus rare mais tout aussi apprécié : n'hésitez pas à documenter votre fichier en ajoutant des informations au début de ce dernier. Voici un exemple :
/**
* @file stack.c
* @author Prénom1 NOM1 (prenom1.nom1@ensiie.eu), Prénom2 NOM2 (prenom2.nom2@ensiie.eu)
* @brief Stack implementation
* @version 0.1
* @date 20XX-YY-ZZ
*
* @copyright Copyright (c) 2026
*
*/
Tests
Quand vous avez des fonctions qui implémentent des petites fonctionnalités, il est appréciable de vérifier si ces dernières font bien ce qu'on veut. On veut faire en sorte que les tests réalisés soient simples : on parle souvent de test unitaire. Par exemple, si on réalise un fichier stack.c, nous souhaitons nous assurer que :
- une pile vide est bien initialisé (test de vacuité),
- les fonctions d'ajout et de retrait d'éléments fonctionnent bien comme on veut (sans effet de bord, sans fuite mémoire, ou autre).
Vous vous en doutez, les tests seraient dans un dossier tests (si nous reprenons l'arborescence précédemment présentée). Pour garder une certaine uniformité, nous préfixerons le nom des fichiers par test_ (exemple : test_graph.c, test_stack.c).
Parmi les outils usuels, on retrouve CUnit pour réaliser des tests unitaires en C, CxxTest ou doctest pour du C++, et unittest de la bibliothèque standard de Python. Toutefois, si vous ne souhaitez pas utiliser de standard externe pour réaliser vos tests unitaires en C/C++, notez qu'il n'existe pas de bibliothèque standard pour faire cela. Donc vous feriez alors des tests relativement simple à la main à base d'instructions if.
README.md
Un fichier README.md est le minimum syndical qui doit figurer dans tout projet. Vous en trouverez systématiquement un dans tous les projets disponibles sur GitHub ou GitLab.
Comme l'indique l'extension du fichier, c'est du Markdown. Il s'agit d'un langage de balisage léger et simple à prendre en main. Si vous n'en avez jamais fait, retrouvez un tuto ici.
Généralement, ce dernier permet de brièvement présenter le projet, sa portée, ce qu'il permet de faire, et comment l'utiliser.
Je vous ai déjà montré un exemple de fichier README.md précédemment. Ici, je vais surtout insister sur le contenu d'un fichier README.md associé à un code.
Pour prendre appui sur le projet de C, il n'y a rien de transcendant : on précise les commandes du Makefile disponible. Par exemple, en anglais :
# PRIM11 - Source code
## Installation
To use this project, you will need:
- **C11** (or later):
- [**gcc compiler**](<https://gcc.gnu.org>).
## Compilation
### Generate main executable
```bash
make
```
### Generate tests executable
```bash
make tests
```
## Usage
### Execution main executable
- Using Makefile:
```bash
make run
```
- Using Shell commands:
```bash
./bin/main 10
```
### Clean project
```bash
make clean
```
## Contact
- Prénom1 NOM1 (`prenom1.nom1@ensiie.eu`)
- Prénom2 NOM2 (`prenom2.nom2@ensiie.eu`)
Comme vous voyez, il n'est pas forcément très volumineux, et il permet à l'utilisateur de savoir comment utiliser votre projet sans devoir regarder le code source. Prenez ce réflexe de toujours laisser ce manuel d'utilisation à jour et suffisamment complet.
Rapport
Je ne peux que vous inviter (et inciter) à découvrir LaTeX. Il s'agit d'un langage et un système de composition de documents. Il est très utilisé dans le monde scientifique, essentiellement pour réaliser des articles, des rapports, voire même des présentations.
À l'école, on vous incitera à utiliser LaTeX pour réaliser la majorité de vos rapports de projet. Il existe sur Internet de nombreux templates/modèles qui préparent la mise en page du document. Vous pouvez alors vous focaliser sur le texte.
Venons alors au contenu d'un rapport. Là aussi, la table des matières variera d'un projet à l'autre. Habituellement, on retrouvera dans un rapport de projet les points suivants :
- brève présentation du sujet et les objectifs du projet,
- la structure du code et les choix réalisés en les justifiant,
- les difficultés rencontrées et comment vous les avez surmontés (si vous avez réussi),
- éventuellement des résultats de votre code (sortie dans le terminal, images générées...),
- une brève bibliographie si vous avez utilisé des ressources extérieures.
Autre
Archive
Quand vous devez envoyer une archive contenant tout votre projet, faites attention à ce que vous mettez dedans !
Typiquement, les fichiers de votre projet générés à la compilation ne doivent pas y figurer (fichiers .o et exécutables, par exemple). De ce fait, pensez à bien faire un make clean si vous ajoutez directement le dossier code.
Pour rappel, trois commandes élémentaires si vous voulez créer l'archive depuis le terminal :
Créer une archive compressée
tar -czvf projet-prim.tar.gz prim-projet/README.md prim-projet/code prim-projet/rapport/main.pdf
où les options correspondent à :
c| Créer une archive,z| Compresser l'archive créée,v| Activer le mode verbeux (pour voir tous les fichiers qui sont ajoutés dans l'archive),f| Spécifier le nom de l'archive (iciprojet-prim.tar.gz).
Lister le contenu d'une archive
tar -tvf projet-prim.tar.gz
où l'option t précise qu'on souhaite lister le contenu de l'archive donnée (ici projet-prim.tar.gz).
Ouvrir une archive
Attention d'avoir placé l'archive dans un dossier temporaire, disons
test_archive, pour éviter d'écraser les fichiers existants.
tar -xvf projet-prim.tar.gz
où l'option x précise qu'on souhaite exécuter l'archive, c'est-à-dire ouvrir l'archive donnée (ici projet-prim.tar.gz).
Conteneurisation
Je vais vous partager mon expérience : j'ai réalisé mon projet de C sur macOS. Le projet compile et s'exécute très bien. Je regarde ma note : 13.5/20. Je pensais avoir une meilleure note, car mon projet semblait fonctionner quasi parfaitement sur l'ensemble des exemples.
Deux ans plus tard, je me suis dit : et si j'essayais de faire tourner mon projet sur Linux ? J'ai donc créé un petit conteneur pour tenter de compiler et d'exécuter mon code. Je remarque alors deux problèmes :
- j'ai oublié de mettre le drapeau
-lmdans mon Makefile pour utiliser la bibliothèquemath.h(alors que cela fonctionnait très bien sans sur macOS), - j'avais une erreur de segmentation dans le
main. C'est simple : mon projet ne fonctionnait tout simplement pas sur Linux.
Ce que je retiens alors : quand je réalise un projet utilisant des langages de bas niveau, je le développe dans un conteneur.
De ce fait, je ne peux que vous inviter à découvrir cette technologie et réaliser vos projets dans ces derniers (surtout ceux utilisant les langages de bas niveau comme C/C++).
Voici alors le Dockerfile qui m'a permis de réaliser mon essai pour le projet de programmation impérative :
# Image
FROM debian:stable-slim
SHELL ["/bin/bash", "-c"]
# Dépendances
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
man \
gcc \
make \
gdb \
valgrind \
linux-perf \
&& apt clean && rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
# Utilisateur non-root "user"
RUN useradd -m -s /bin/bash user && usermod -aG sudo user \
&& echo "user ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
# Dossier de travail
WORKDIR /workspace
# Utilisateur user
USER user
# Commande par défaut exécutée au lancement du conteneur
CMD ["bash"]
et les commandes à exécuter dans le terminal pour créer et entrer dans le conteneur sont les suivantes :
docker build -t prim-container .
docker run -it -v ${PWD}/code:/workspace prim-container bash
Conclusion
De toute évidence, je ne détiens pas la vérité absolue. Vous êtes libres d'appliquer ou non mes conseils.
Je vous souhaite bon courage dans la réalisation de votre projet !