Outils¶
Pour le cours de programmation système, nous travaillerons sous Linux avec l’éditeur de code Visual Studio Code / VSCode.
Pour compiler, nous utiliserons GCC (GNU Compiler Collection) ; Clang (C language family frontend for LLVM) fonctionne aussi.
Les commandes d’installation des extensions VSCode sont données dans les consignes. Les deux extensions utilisées sont :
C/C++ Extension Pack : pour IntelliSense, la navigation dans le code, le debug, etc. ;
Clang-Format : pour le formatage automatique du code avec clang-format.
Installation sur votre machine perso¶
Sous Linux ou macOS¶
Paquets à installer :
Debian/Ubuntu :
sudo apt update sudo apt install build-essential gdb valgrind clang-format strace htop hwloc xxd netcat-openbsd
Fedora :
sudo dnf group install development-tools sudo dnf install gdb valgrind clang-tools-extra strace htop hwloc xxd nmap-ncat libasan libubsan
Arch :
sudo pacman -S base-devel gdb valgrind clang strace htop hwloc tinyxxd openbsd-netcat
macOS (avec Homebrew) : configuration non testée.
valgrind,gdbetstracene sont pas disponibles sur les versions récentes de macOS (en particulier sur Apple Silicon) : utilisez-fsanitize=address(voir Analyse à l’exécution) etlldbà la place ;-fsanitize=addressn’y détecte pas les fuites mémoire, utilisez pour celaleaks --atExit -- ./mon_prog. Attention,gccy est un alias declang(le GCC de Homebrew s’appellegcc-14,gcc-15, etc.) :brew install gcc clang-format
Sous Windows¶
C’est un cours de programmation système sous Unix. La plupart des TP (fichiers POSIX, mémoire, processus, threads, sockets) ne compileront pas nativement sous Windows : fork, unistd.h, sys/socket.h ou /proc n’y existent pas.
Vous pouvez au choix :
installer Linux en dual boot ;
passer par WSL (solution conseillée, accessible directement depuis VSCode) ;
utiliser un conteneur Docker (image
ubuntupar exemple) ;utiliser une machine virtuelle (par exemple avec VirtualBox).
Dans les quatre cas, faites-le avant de venir en cours ; sinon, utilisez les PC des salles.
Compilation¶
La compilation d’un programme C se déroule en 4 étapes :
Prétraitement – le préprocesseur traite les directives comme
#include,#defineet produit du C « pur », sans macros ni includes (fichier.i, normalement temporaire) ;Compilation – le compilateur transforme ce fichier en assembleur (fichier
.s) ;Assemblage – l’assembleur (
as) transforme le code assembleur en code machine dans un fichier objet.o;Édition de liens (linking) – l’éditeur de liens assemble les objets et les bibliothèques (dont la libc) en un programme final.
Voir les commandes de chaque étape
L’option indiquée sous chaque flèche arrête gcc après l’étape correspondante :
gcc -E fichier.c -o fichier.i # prétraitement
gcc -S fichier.i -o fichier.s # compilation
gcc -c fichier.s -o fichier.o # assemblage
gcc fichier.o -o programme # édition de liens
En pratique, ces étapes sont enchaînées automatiquement pour produire un exécutable :
# avec gcc, éventuellement gcc-VERSION
gcc -std=c2x -Wall -Wextra -pedantic -g mon_prog.c -o mon_prog
# ou avec clang, éventuellement clang-VERSION
clang -std=c2x -Wall -Wextra -pedantic -g mon_prog.c -o mon_prog
# puis exécution avec
./mon_prog
Options fréquentes :
Option |
Effet |
|---|---|
|
Active les avertissements de base |
|
Avertissements supplémentaires |
|
Avertit des écarts à la norme choisie par |
|
Utilisation de la norme C23 |
|
Ajoute les informations de debug pour |
|
Active les optimisations |
|
Définit le nom du fichier de sortie |
|
Définit la macro |
|
Compile et lie avec la bibliothèque des threads POSIX |
|
Ajoute des vérifications à l’exécution (voir ci-dessous) |
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, etc.), deux outils peuvent être utilisés :
valgrind (compiler avec
-gpour 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 -s ./mon_prog
Si le
LEAK SUMMARYaffiché vers la fin indique des octets definitely lost ou indirectly lost, il y a une fuite mémoire (la catégorie still reachable, mémoire encore accessible à la fin du programme, n’est pas forcément un bug) :==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 toute la mémoire allouée a été libérée, le
HEAP SUMMARYaffiche autant d’allocations (allocs) que de libérations (frees), et valgrind conclut par « All heap blocks were freed » :==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 ==336877== ==336877== All heap blocks were freed -- no leaks are possible
D’autres erreurs peuvent être affichées lors de l’exécution.
AddressSanitizer et UndefinedBehaviorSanitizer : activés par les options
-fsanitize=address,undefined -fno-sanitize-recover=alldegcc:# 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
AddressSanitizer:DEADLYSIGNALs’affiche à répétition, faitesCtrl+Cet relancez (peut planter plusieurs fois, ne devrait pas arriver avec les versions récentes des outils).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 0x7d0628d2c30f in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:67 #1 0x5b257cd9d21e in main /home/user/mon_prog.c:4 #2 0x7d0628029d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58 SUMMARY: AddressSanitizer: 400 byte(s) leaked in 1 allocation(s).
La ligne
#1indique le fichier et la ligne de l’allocation (mon_prog.c:4), grâce à l’option-g.Contrairement à valgrind, les sanitizers détectent aussi les accès hors limites des tableaux dans le stack, par exemple :
1#include <stdio.h> 2 3int main(void) { 4 int tab[5]; 5 tab[10] = 4; 6 printf("tab[10] = %d\n", tab[10]); 7 return 0; 8}
À l’exécution, c’est ici
UndefinedBehaviorSanitizer(UBSan, activé par la partieundefinedde-fsanitize=address,undefined) qui signale l’erreur :mon_prog.c:5:8: runtime error: index 10 out of bounds for type 'int [5]'valgrind (Memcheck), lui, ne détecte pas ce débordement : il ne connaît pas les limites des tableaux dans le stack ou globaux, seulement celles des blocs alloués dans la heap.
Avertissement
Ne lancez pas valgrind sur un exécutable compilé avec -fsanitize : les deux outils ne fonctionnent pas ensemble. Recompilez sans -fsanitize avant de lancer valgrind.
Pour les programmes multithreads (voir Partie 5 - Threads), l’outil helgrind de valgrind détecte les data races (programme compilé sans -fsanitize) :
gcc -std=c2x -Wall -Wextra -pedantic -g -pthread mon_prog.c -o mon_prog
valgrind --tool=helgrind ./mon_prog
ThreadSanitizer (option -fsanitize=thread, incompatible avec -fsanitize=address) détecte aussi les data races, sans valgrind.
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. Ils permettent d’uniformiser le style de code entre plusieurs projets/groupes/classes, 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 changements de format.
clang-format lit les règles de formatage dans le fichier .clang-format. Ce fichier et le fichier .vscode/settings.json (formatage à l’enregistrement) sont fournis dans les consignes.
Une fois la configuration en place, le code suivant :
#include <stdio.h>
int main( void )
{
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(void) {
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 p1/*.c
L’option -i applique les modifications directement dans les fichiers.
Refaire la configuration soi-même
Pour générer un fichier .clang-format :
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 (C++ uniquement, sans effet en C)
BinPackArguments: true -> false
BinPackParameters: true -> false
ConstructorInitializerAllOnOneLineOrOnePerLine: false -> true (C++ uniquement, option dépréciée)
IndentWidth: 2 -> 4
ce qui donne le fichier .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---
Pour formater automatiquement à l’enregistrement : File > Preferences > Settings (ou Ctrl+, sous Linux), chercher format on save puis activer Editor: Format On Save.
Pour définir Clang-Format comme formateur par défaut, avec un fichier .c ouvert :
ouvrez la palette avec
Ctrl+Shift+P;tapez
Format Document With;sélectionnez
Configure Default Formatter;choisissez
Clang-Format.
Pour formater sans enregistrer, cherchez Format Document dans la palette : le raccourci, s’il est défini, est indiqué à droite de la ligne.
Pour un réglage par projet, le fichier .vscode/settings.json (à créer si non existant) contient :
{
"editor.formatOnSave": true,
"[c]": {
"editor.defaultFormatter": "xaver.clang-format"
},
"[cpp]": {
"editor.defaultFormatter": "xaver.clang-format"
},
"clang-format.style": "file"
}
Debug¶
Il existe plusieurs debuggers en ligne de commande, comme gdb ou lldb.
VSCode propose un debugger pratique et rapide à utiliser pour un fichier :
Autres outils¶
Documentation¶
Pour générer les templates de docstring Doxygen, utilisez l’extension Doxygen Documentation Generator.
# installation depuis le terminal :
code --install-extension cschlosser.doxdocgen
Ou depuis la palette de VSCode (Ctrl+P) :
ext install cschlosser.doxdocgen
Ensuite, pour ajouter une docstring à une fonction, tapez /** puis appuyez sur Entrée :
// 1 taper /** (le */ s'ajoute automatiquement)
/** */
int my_function(int a, float b) {
// ...
}
// 2 appuyer sur "Entrée"
/**
* @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 des erreurs¶
Error Lens permet d’afficher les erreurs directement sur la ligne concernée. Il peut être envahissant si vous utilisez un correcteur orthographique et nommez vos variables en français : il signalera toutes les variables écrites sans accent.
Compilation de projets : Make et CMake¶
Les exercices du cours tiennent en un seul fichier : une commande gcc suffit. Pour un projet de plusieurs fichiers, on automatise la compilation :
Make lit un fichier
Makefilequi décrit comment compiler chaque fichier et ne recompile que les fichiers modifiés ;CMake génère ces
Makefileà partir d’un fichierCMakeLists.txtplus simple à écrire, et s’intègre dans VSCode avec l’extension CMake Tools.
Analyse statique¶
Des outils d’analyse statique lisent le code sans l’exécuter et signalent des bugs que le compilateur ne voit pas (pointeur NULL déréférencé, fuite sur un chemin d’erreur, variable non initialisée…) : cppcheck et clang-tidy.
# --suppress : masque les messages inutiles sur les includes système
cppcheck --enable=all --language=c --quiet --suppress=missingIncludeSystem --suppress=checkersReport p1/p1e3.c
Outils en ligne de commande¶
strace: affiche les appels système faits par un programme (voir l”introduction) ;xxd,hexdump,od: affichent le contenu d’un fichier binaire octet par octet (voir Partie 2 - Fichiers et l’exercice p2e5) ;/proc/PID/maps: affiche les zones mémoire d’un processus (voir Partie 3 - Mémoire) ;htop,ps,pstree: affichent les processus en cours (voir Partie 4 - Processus) ;nc(netcat) : client ou serveur TCP minimal, pour tester un serveur sans écrire de client (nc 127.0.0.1 8080) ou un client sans écrire de serveur (nc -l 8080) (voir Partie 6 - Sockets) ;ss -tlnp: liste les ports TCP en écoute et les programmes associés (voir Partie 6 - Sockets).