Outils

Pour le cours de programmation système, nous travaillerons sous Linux avec l’éditeur de code Visual Studio Code / VSCode.

Si, sur votre ordinateur personnel, vous êtes sous Windows, je vous recommande d’utiliser WSL pour travailler avec un environnement Linux, accessible directement depuis VSCode.

Pour compiler nous utiliserons GCC (GNU Compiler Collection) (possible aussi avec Clang (C language family frontend for LLVM)).

Installation des différents outils utiles au développement (hormis VSCode) sous Debian/Ubuntu :

sudo apt update
sudo apt install build-essential clang cmake cppcheck valgrind gdb lldb clang-tidy

Dans VSCode, installez les extensions suivantes :

  • C/C++ Extension Pack : pour IntelliSense, la navigation dans le code, le debug, la compilation, …

    # installation depuis le terminal :
    code --install-extension ms-vscode.cpptools-extension-pack
    # installation depuis le panel VSCode (Ctrl+P) :
    ext install ms-vscode.cpptools-extension-pack
    
  • Clang-Format : pour le formatage automatique du code avec clang-format.

    # installation depuis le terminal :
    code --install-extension xaver.clang-format
    # installation depuis le panel VSCode (Ctrl+P) :
    ext install xaver.clang-format
    

Compilation

La compilation d’un programme C se déroule en 3 étapes :

  1. Prétraitement – le préprocesseur traite les directives comme #include, #define et produit un fichier temporaire .i contenant du C « pur », sans macros ni includes.

    gcc -E fichier.c -o fichier.i
    
  2. Compilation – le compilateur transforme ce fichier en assembleur .s

    gcc -S fichier.i -o fichier.s
    
  3. Assemblage - puis transforme l’assembleur en code binaire dans un fichier objet .o

gcc -c fichier.s -o fichier.o
  1. Édition de liens (linking) – l’éditeur de liens assemble les objets et les bibliothèques en un programme final.

    gcc fichier.o -o programme
    

En pratique, ces étapes sont enchaînées automatiquement pour produire un exécutable :

# avec gcc, éventuellement gcc-VERSION
gcc -Wall -Wextra -pedantic -std=c2x monprog.c -o monprog
# ou avec clang, éventuellement clang-VERSION
clang -Wall -Wextra -pedantic -std=c2x monprog.c -o monprog
# puis execution avec
./monprog

Options fréquentes :

Option

Effet

-Wall

Active les avertissements de base

-Wextra

Avertissements supplémentaires

-pedantic

Respect strict de la norme

-std=c2x

Utilisation de la norme C23

-g

Ajoute les infos de debug pour gdb ou lldb

-O2/-O3

Active les optimisations

-c

Compile sans édition de liens → produit uniquement un fichier .o

-o fichier

Définit le nom du fichier de sortie

Analyse à l’exécution

Pour détecter les problèmes de mémoire, en particulier lors de l’utilisation de pointeurs (fuites, accès invalides,…), deux outils peuvent être utilisés :

  • valgrind (compiler avec -g pour inclure les informations de debug) :

    # compilation
    gcc -std=c2x -Wall -Wextra -pedantic -g mon_prog.c -o mon_prog
    # exécution d'un programme avec détection des fuites mémoire
    valgrind ./mon_prog
    
    # pour un rapport plus détaillé avec les emplacements dans le code source
    valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./monprog
    

    S’il y a une partie LEAK SUMMARY vers la fin, il y a un problème au niveau de l’allocation :

    ==340142== HEAP SUMMARY:
    ==340142==     in use at exit: 400 bytes in 1 blocks
    ==340142==   total heap usage: 3 allocs, 2 frees, 2,448 bytes allocated
    ==340142==
    ==340142== LEAK SUMMARY:
    ==340142==    definitely lost: 400 bytes in 1 blocks
    ==340142==    indirectly lost: 0 bytes in 0 blocks
    ==340142==      possibly lost: 0 bytes in 0 blocks
    ==340142==    still reachable: 0 bytes in 0 blocks
    ==340142==         suppressed: 0 bytes in 0 blocks
    

    Si tout va bien (sur l’allocation), le HEAP SUMMARY affiche qu’il y a autant d’allocs que de free :

    ==336877== HEAP SUMMARY:
    ==336877==     in use at exit: 0 bytes in 0 blocks
    ==336877==   total heap usage: 3 allocs, 3 frees, 2,448 bytes allocated
    

    D’autres erreurs peuvent être affichés lors de l’exécution.

  • AddressSanitizer : activé dans le CMakeLists.txt (ligne 65) lors d’une compilation en mode debug grâce aux options : -fsanitize=address,undefined -fno-sanitize-recover=all.

    Lors d’une compilation à la main (en cas d’affichage de AddressSanitizer:DEADLYSIGNAL à répétition, faire Ctrl+C puis relancer, peut arriver plusieurs fois) :

    # compilation
    gcc -std=c2x -Wall -Wextra -pedantic -g -fsanitize=address,undefined -fno-sanitize-recover=all mon_prog.c -o mon_prog
    # exécution
    ./mon_prog
    # si beaucoup de 'AddressSanitizer:DEADLYSIGNAL' s'affichent faire Ctrl+C puis relancer
    # (peut arriver plusieurs fois de suite)
    

    Le message en cas d’erreur est clair avec AddressSanitizer :

    =================================================================
    ==331863==ERROR: LeakSanitizer: detected memory leaks
    
    Direct leak of 400 byte(s) in 1 object(s) allocated from:
        #0 0x7609d7adefdf in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
        #1 0x64dcf0ad14a5 in main (path/p1_e4+0x24a5) (BuildId: 4b6a256d8842ed960cc21b54141f97e11cd9ef36)
        #2 0x7609d6e29d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    
    SUMMARY: AddressSanitizer: 400 byte(s) leaked in 1 allocation(s).
    

    AddressSanitizer détecte mieux les accès en dehors des limites d’un tableau, par exemple :

    1#include <stdio.h>
    2
    3int main() {
    4    int tab[5];
    5    tab[10] = 4;
    6    printf("tab[10] = %d\n", tab[10]);
    7    return 0;
    8}
    

    AddressSanitizer affiche (à l’exécution) :

    mon_fichier.c:5:8: runtime error: index 10 out of bounds for type 'int [5]'
    

    Là où valgrind ne le détecte pas systématiquement.

Formatage automatique

Pour maintenir un code lisible, cohérent et conforme à de bonnes pratiques, les outils de formatage automatique comme clang-format sont très utiles. Il permet d’uniformiser le style de code entre plusieurs projets/groupes/classe, de gagner du temps en évitant les discussions inutiles liées à la mise en forme du code et d’éviter les commits sur git avec uniquement des changement de format.

Il utilise le fichier .clang-format pour lire les règles de formatage à utiliser. Pour le générer, vous pouvez utiliser la ligne suivante :

clang-format -style=llvm -dump-config > .clang-format

Personnellement, j’ai modifié les options suivantes (qui se rapprochent plus d’un formatage classique en Python ou Rust) :

AllowShortEnumsOnASingleLine: true -> false
AllowShortFunctionsOnASingleLine: All -> None
AllowShortLambdasOnASingleLine: All -> None
BinPackArguments: true -> false
BinPackParameters: true -> false
ConstructorInitializerAllOnOneLineOrOnePerLine: false -> true
IndentWidth: 2 -> 4

ce qui donne le fichier .clang-format :

Dans VSCode, l’extension Clang-Format permet de formater le code automatiquement à la sauvegarde. Pour automatiser le formatage lorsque le fichier est enregistré : File>Preferences>Settings (ou Ctrl+, sous Linux), chercher format on save puis activer Editor: Format On Save.

Puis lorsqu’un fichier .c est ouvert, appuyez sur Ctrl+Shift+P puis tapez format document with puis sélectionnez Configure Default Formatter puis Clang-Format. Vous pouvez aussi faire Ctrl+Shift+I et définir Clang-Format comme formateur par défaut.

Pour un réglage par projet : dans le fichier .vscode/settings.json (à créer si non existant), ajouter :

{
    "editor.formatOnSave": true,
    "[c]": {
        "editor.defaultFormatter": "xaver.clang-format"
    },
    "[cpp]": {
        "editor.defaultFormatter": "xaver.clang-format"
    },
    "clang-format.style": "file",
}

Si ça fonctionne, le code :

#include <stdio.h>

int main(         )
{
int a=    0;
            int b   = 4 ;

for   (  int i = 0;
i< 10;   ++ i)
{


printf(
    "%d",        i
);
}
return 0           ;
}

sera automatiquement formaté en :

#include <stdio.h>

int main() {
    int a = 0;
    int b = 4;

    for (int i = 0; i < 10; ++i) {

        printf("%d", i);
    }
    return 0;
}

Pour l’utiliser dans la ligne de commande :

clang-format -i src/*.c src/*.h

L’option -i applique les modifications directement dans les fichiers.

Debug

Il existe plusieurs outils pour le debug dans la ligne de commande comme gdb ou lldb.

VSCode propose un debugger pratique et rapide à utiliser pour un fichier :

Pour des projets avec plus d’un fichier, utiliser les outils intégrés avec l’extension CMake est plus adapté. Je vous invite à voir ce tutoriel 🇫🇷/ 🇬🇧 pour plus de détails.

Automatisation : Make et CMake

Make est un outil d’automatisation de la compilation. Il utilise des fichiers Makefile pour décrire les règles de compilation et les dépendances. Seuls les fichiers modifiés depuis la dernière compilation sont recompilés.

# installation depuis le terminal :
code --install-extension ms-vscode.makefile-tools
# installation depuis le panel VSCode (Ctrl+P) :
ext install ms-vscode.makefile-tools

CMake est un générateur de build multiplate-forme. Il permet de générer des Makefile et la configuration des outils utiles au développement. Il s’intègre aussi très bien dans les IDE (VSCode, CLion, …) et permet de gérer des projets complexes.

  • CMake : pour le support de CMake.

  • CMake Tools : pour plus d’outils autour de CMake.

# installation depuis le terminal :
code --install-extension twxs.cmake
code --install-extension ms-vscode.cmake-tools
# installation depuis le panel VSCode (Ctrl+P) :
ext install twxs.cmake
ext install ms-vscode.cmake-tools

Ci-dessous, un exemple de fichier CMakeLists.txt, lorsque des nouveaux fichiers s’ajoutent au projet, il faut les ajouter sur les lignes 5-8, de la même manière que les fichiers main.c et utils.c de l’exemple :

Pour gérer CMake à la main :

# créer et se déplacer dans un dossier build
mkdir -p build
cd build

# génération des fichiers Makefile à partir du CMakeLists.txt
cmake ..

# compilation du projet
cmake --build .

# compilation du projet en mode debug ou release
cmake -DCMAKE_BUILD_TYPE=Debug ..
cmake -DCMAKE_BUILD_TYPE=Release ..

Analyse statique

Pour aider à détecter des bugs que le compilateur ne voit pas, nous pouvons utiliser clang-tidy (déjà présent dans le fichier CMakeLists.txt aux lignes 85-89 et avec le fichier .clang-tidy) et cppcheck.

# analyser récursivement un dossier (ex: src/) avec des messages détaillés
# et supprime les messages inutiles sur les includes système
cppcheck --enable=all --inconclusive --std=c23 --language=c --quiet --suppress=missingIncludeSystem src/

Pour un projet complexe ou hors CMake, vous pouvez générer le fichier compile_commands.json avec :

cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ..

Ce fichier permet à clang-tidy de connaître les options de compilation de chaque fichier source.

Autres outils

Docstring

Pour générer les templates de docstring, Doxygen Documentation Generator :

Depuis la ligne de commande :

code --install-extension cschlosser.doxdocgen

Depuis le panel VSCode (Ctrl+P) :

ext install cschlosser.doxdocgen

Ensuite, pour ajouter une docstring à une fonction, taper /** puis enter :

// 1 taper /** (le */ s'ajoute automatiquement)
/** */
int my_function(int a, float b) {
    // ...
}

// 2 presser "Entrer"
/**
* @brief
*
* @param a
* @param b
* @return int
*/
int my_function(int a, float b) {
    // ...
}

Ensuite, quand vous passerez le curseur sur la fonction, vous verrez les informations sur ce que fait la fonction et les différents paramètres.

Affichage erreurs

Error Lens permet d’afficher les erreurs directement sur la ligne, peut être envahissant si vous codez en français avec un spell checker qui mettra en avant toutes les variables où il y a une faute car pas accent.