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 :
Prétraitement – le préprocesseur traite les directives comme
#include,#defineet produit un fichier temporaire.icontenant du C « pur », sans macros ni includes.gcc -E fichier.c -o fichier.i
Compilation – le compilateur transforme ce fichier en assembleur
.sgcc -S fichier.i -o fichier.s
Assemblage - puis transforme l’assembleur en code binaire dans un fichier objet
.o
gcc -c fichier.s -o fichier.o
É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 |
|
Active les avertissements de base |
|
Avertissements supplémentaires |
|
Respect strict de la norme |
|
Utilisation de la norme C23 |
|
Ajoute les infos de debug pour |
|
Active les optimisations |
|
Compile sans édition de liens → produit uniquement un fichier .o |
|
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 :
Voir .clang-format
1--- 2Language: Cpp 3# BasedOnStyle: LLVM 4AccessModifierOffset: -2 5AlignAfterOpenBracket: Align 6AlignArrayOfStructures: None 7AlignConsecutiveAssignments: 8 Enabled: false 9 AcrossEmptyLines: false 10 AcrossComments: false 11 AlignCompound: false 12 PadOperators: true 13AlignConsecutiveBitFields: 14 Enabled: false 15 AcrossEmptyLines: false 16 AcrossComments: false 17 AlignCompound: false 18 PadOperators: false 19AlignConsecutiveDeclarations: 20 Enabled: false 21 AcrossEmptyLines: false 22 AcrossComments: false 23 AlignCompound: false 24 PadOperators: false 25AlignConsecutiveMacros: 26 Enabled: false 27 AcrossEmptyLines: false 28 AcrossComments: false 29 AlignCompound: false 30 PadOperators: false 31AlignEscapedNewlines: Right 32AlignOperands: Align 33AlignTrailingComments: true 34AllowAllArgumentsOnNextLine: true 35AllowAllParametersOfDeclarationOnNextLine: true 36AllowShortEnumsOnASingleLine: false 37AllowShortBlocksOnASingleLine: Never 38AllowShortCaseLabelsOnASingleLine: false 39AllowShortFunctionsOnASingleLine: None 40AllowShortLambdasOnASingleLine: None 41AllowShortIfStatementsOnASingleLine: Never 42AllowShortLoopsOnASingleLine: false 43AlwaysBreakAfterDefinitionReturnType: None 44AlwaysBreakAfterReturnType: None 45AlwaysBreakBeforeMultilineStrings: false 46AlwaysBreakTemplateDeclarations: MultiLine 47AttributeMacros: 48 - __capability 49BinPackArguments: false 50BinPackParameters: false 51BraceWrapping: 52 AfterCaseLabel: false 53 AfterClass: false 54 AfterControlStatement: Never 55 AfterEnum: false 56 AfterFunction: false 57 AfterNamespace: false 58 AfterObjCDeclaration: false 59 AfterStruct: false 60 AfterUnion: false 61 AfterExternBlock: false 62 BeforeCatch: false 63 BeforeElse: false 64 BeforeLambdaBody: false 65 BeforeWhile: false 66 IndentBraces: false 67 SplitEmptyFunction: true 68 SplitEmptyRecord: true 69 SplitEmptyNamespace: true 70BreakBeforeBinaryOperators: None 71BreakBeforeConceptDeclarations: Always 72BreakBeforeBraces: Attach 73BreakBeforeInheritanceComma: false 74BreakInheritanceList: BeforeColon 75BreakBeforeTernaryOperators: true 76BreakConstructorInitializersBeforeComma: false 77BreakConstructorInitializers: BeforeColon 78BreakAfterJavaFieldAnnotations: false 79BreakStringLiterals: true 80ColumnLimit: 80 81CommentPragmas: "^ IWYU pragma:" 82QualifierAlignment: Leave 83CompactNamespaces: false 84ConstructorInitializerIndentWidth: 4 85ContinuationIndentWidth: 4 86Cpp11BracedListStyle: true 87DeriveLineEnding: true 88DerivePointerAlignment: false 89DisableFormat: false 90EmptyLineAfterAccessModifier: Never 91EmptyLineBeforeAccessModifier: LogicalBlock 92ExperimentalAutoDetectBinPacking: false 93PackConstructorInitializers: BinPack 94BasedOnStyle: "" 95ConstructorInitializerAllOnOneLineOrOnePerLine: true 96AllowAllConstructorInitializersOnNextLine: true 97FixNamespaceComments: true 98ForEachMacros: 99 - foreach 100 - Q_FOREACH 101 - BOOST_FOREACH 102IfMacros: 103 - KJ_IF_MAYBE 104IncludeBlocks: Preserve 105IncludeCategories: 106 - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 107 Priority: 2 108 SortPriority: 0 109 CaseSensitive: false 110 - Regex: '^(<|"(gtest|gmock|isl|json)/)' 111 Priority: 3 112 SortPriority: 0 113 CaseSensitive: false 114 - Regex: ".*" 115 Priority: 1 116 SortPriority: 0 117 CaseSensitive: false 118IncludeIsMainRegex: "(Test)?$" 119IncludeIsMainSourceRegex: "" 120IndentAccessModifiers: false 121IndentCaseLabels: false 122IndentCaseBlocks: false 123IndentGotoLabels: true 124IndentPPDirectives: None 125IndentExternBlock: AfterExternBlock 126IndentRequiresClause: true 127IndentWidth: 4 128IndentWrappedFunctionNames: false 129InsertBraces: false 130InsertTrailingCommas: None 131JavaScriptQuotes: Leave 132JavaScriptWrapImports: true 133KeepEmptyLinesAtTheStartOfBlocks: true 134LambdaBodyIndentation: Signature 135MacroBlockBegin: "" 136MacroBlockEnd: "" 137MaxEmptyLinesToKeep: 1 138NamespaceIndentation: None 139ObjCBinPackProtocolList: Auto 140ObjCBlockIndentWidth: 2 141ObjCBreakBeforeNestedBlockParam: true 142ObjCSpaceAfterProperty: false 143ObjCSpaceBeforeProtocolList: true 144PenaltyBreakAssignment: 2 145PenaltyBreakBeforeFirstCallParameter: 19 146PenaltyBreakComment: 300 147PenaltyBreakFirstLessLess: 120 148PenaltyBreakOpenParenthesis: 0 149PenaltyBreakString: 1000 150PenaltyBreakTemplateDeclaration: 10 151PenaltyExcessCharacter: 1000000 152PenaltyReturnTypeOnItsOwnLine: 60 153PenaltyIndentedWhitespace: 0 154PointerAlignment: Right 155PPIndentWidth: -1 156ReferenceAlignment: Pointer 157ReflowComments: true 158RemoveBracesLLVM: false 159RequiresClausePosition: OwnLine 160SeparateDefinitionBlocks: Leave 161ShortNamespaceLines: 1 162SortIncludes: CaseSensitive 163SortJavaStaticImport: Before 164SortUsingDeclarations: true 165SpaceAfterCStyleCast: false 166SpaceAfterLogicalNot: false 167SpaceAfterTemplateKeyword: true 168SpaceBeforeAssignmentOperators: true 169SpaceBeforeCaseColon: false 170SpaceBeforeCpp11BracedList: false 171SpaceBeforeCtorInitializerColon: true 172SpaceBeforeInheritanceColon: true 173SpaceBeforeParens: ControlStatements 174SpaceBeforeParensOptions: 175 AfterControlStatements: true 176 AfterForeachMacros: true 177 AfterFunctionDefinitionName: false 178 AfterFunctionDeclarationName: false 179 AfterIfMacros: true 180 AfterOverloadedOperator: false 181 AfterRequiresInClause: false 182 AfterRequiresInExpression: false 183 BeforeNonEmptyParentheses: false 184SpaceAroundPointerQualifiers: Default 185SpaceBeforeRangeBasedForLoopColon: true 186SpaceInEmptyBlock: false 187SpaceInEmptyParentheses: false 188SpacesBeforeTrailingComments: 1 189SpacesInAngles: Never 190SpacesInConditionalStatement: false 191SpacesInContainerLiterals: true 192SpacesInCStyleCastParentheses: false 193SpacesInLineCommentPrefix: 194 Minimum: 1 195 Maximum: -1 196SpacesInParentheses: false 197SpacesInSquareBrackets: false 198SpaceBeforeSquareBrackets: false 199BitFieldColonSpacing: Both 200Standard: Latest 201StatementAttributeLikeMacros: 202 - Q_EMIT 203StatementMacros: 204 - Q_UNUSED 205 - QT_REQUIRE_VERSION 206TabWidth: 8 207UseCRLF: false 208UseTab: Never 209WhitespaceSensitiveMacros: 210 - STRINGIZE 211 - PP_STRINGIZE 212 - BOOST_PP_STRINGIZE 213 - NS_SWIFT_NAME 214 - CF_SWIFT_NAME 215---
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.
Makefile Tools : pour le support de Make.
# 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 :
Voir CMakeLists.txt
1cmake_minimum_required(VERSION 3.22) 2project(my_c_project LANGUAGES C) 3 4# List all sources files 5set(PROJECT_SOURCE_FILES 6 ${CMAKE_SOURCE_DIR}/src/main.c 7 ${CMAKE_SOURCE_DIR}/src/utils.c 8) 9 10# Use clang-tidy if available, you may need to edit this with the correct version of clang-tidy 11find_program(CLANG_TIDY_EXE NAMES "clang-tidy-19" "clang-tidy") 12if(CLANG_TIDY_EXE) 13 message(STATUS "Found clang-tidy: ${CLANG_TIDY_EXE}") 14 set(CMAKE_C_CLANG_TIDY "${CLANG_TIDY_EXE}") 15endif() 16 17# Use modern C 18set(CMAKE_C_STANDARD 23) 19set(CMAKE_C_STANDARD_REQUIRED ON) 20 21# Default to Release if not specified 22if(NOT CMAKE_BUILD_TYPE) 23 set(CMAKE_BUILD_TYPE "Release") 24endif() 25 26# Detect compiler 27if(CMAKE_C_COMPILER_ID MATCHES "Clang") 28 set(COMPILER_IS_CLANG TRUE) 29elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU") 30 set(COMPILER_IS_GCC TRUE) 31endif() 32 33# Enable LTO in Release mode 34if(CMAKE_BUILD_TYPE MATCHES Release) 35 set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) 36 37 if(COMPILER_IS_GCC) 38 set(CMAKE_AR "gcc-ar") 39 set(CMAKE_RANLIB "gcc-ranlib") 40 endif() 41endif() 42 43 44# Base warning flags 45set(WARN_FLAGS_COMMON 46 -Wall 47 -Wextra 48 -Wshadow 49 -Wcast-align 50 -Wunused 51 -pedantic 52 -Wconversion 53 -Wmisleading-indentation 54 -Wnull-dereference 55 -Wdouble-promotion 56 -Wformat=2 57 -Werror 58) 59 60add_executable(${PROJECT_NAME} ${PROJECT_SOURCE_FILES}) 61 62# Apply flags per configuration with conditional logic 63target_compile_options(${PROJECT_NAME} PRIVATE 64 # Common flags 65 $<$<CONFIG:Debug>:${WARN_FLAGS_COMMON} -O0 -g -DDEBUG -fsanitize=address,undefined -fno-sanitize-recover=all> 66 $<$<CONFIG:Release>:${WARN_FLAGS_COMMON} -O3 -DNDEBUG -flto=auto -s> 67) 68 69# Link sanitizers too for debug mode 70target_link_options(${PROJECT_NAME} PRIVATE 71 $<$<CONFIG:Debug>:-fsanitize=address,undefined -fno-sanitize-recover=all> 72)
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.