Débogage : Apprendre à utiliser les outils de débogage (gdb
pour C et pdb
pour Python) pour identifier et corriger les erreurs dans les programmes.
Chaîne de compilation : Comprendre le processus complet de compilation en C avec gcc
, y compris les étapes de prétraitement, compilation, assemblage et édition des liens.
Inspection de code : Utiliser dis
en Python pour inspecter le bytecode et comprendre comment le code source est transformé en instructions exécutables.
Manipulation de chaînes de caractères : Maîtriser les techniques de manipulation de chaînes de caractères en C et en Python, incluant les opérations de recherche, découpage, formatage et gestion des encodages.
Gestion des données et recherche de phrases : Comparer la gestion des structures de données en C (structures) et en Python (classes) pour la gestion d’informations comme les données d’étudiant.e.s, et implémenter des techniques pour rechercher des phrases spécifiques dans des fichiers en utilisant C et Python.
Cet exercice présente une brève introduction à gdb
et explique comment déboguer votre programme C à l’aide de gdb
.
Le code suivant (erreurs.c) présente une erreur majeure : il tente d’accéder à des indices de tableau supérieurs à la taille maximale du tableau (100).
#include <stdio.h>
int main() {
int tableau[100];
for (int compteur = 0; compteur < sizeof(tableau); compteur++) { //Erreur
tableau[compteur] = tableau[compteur] * 2;
}
return (0);
}
Pour la compilation et la création d’un exécutable
$ gcc erreurs.c
Lorsque nous exécutons le code, nous obtenons l’erreur suivante et le programme se plante (comme prévu).
./a.out
*** stack smashing detected ***: terminated
fish: Job 1, './a.out' terminated by signal SIGABRT (Abort)
Pour déboguer ce code et trouver la source de l’erreur, nous devrons d’abord le compiler et ajouter des informations supplémentaires pour le débogage avec l’option -ggdb3
.
$ gcc -ggdb3 erreurs.c
Nous allons maintenant exécuter le code avec gdb.
$ gdb a.out
GNU gdb (Ubuntu 12.0.90-0ubuntu1) 12.0.90
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from a.out...
(gdb)
Une fois que vous exécutez gdb, vous verrez une autre ligne de commande avec (gdb)
.
Tapez r
pour exécuter le programme.
(gdb) r
Starting program: ./ProgC/gdb/a.out
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
*** stack smashing detected ***: terminated
Program received signal SIGABRT, Aborted.
__pthread_kill_implementation (no_tid=0, signo=6, threadid=140737351481152) at ./nptl/pthread_kill.c:44
44 ./nptl/pthread_kill.c: No such file or directory.
Le programme s’est écrasé et pour trouver la ligne qui est la raison de l’écrasement, tapez bt
.
(gdb) bt
#0 __pthread_kill_implementation (no_tid=0, signo=6, threadid=140737351481152) at ./nptl/pthread_kill.c:44
#1 __pthread_kill_internal (signo=6, threadid=140737351481152) at ./nptl/pthread_kill.c:78
#2 __GI___pthread_kill (threadid=140737351481152, signo=signo@entry=6) at ./nptl/pthread_kill.c:89
#3 0x00007ffff7dbc476 in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26
#4 0x00007ffff7da27f3 in __GI_abort () at ./stdlib/abort.c:79
#5 0x00007ffff7e036f6 in __libc_message (action=action@entry=do_abort, fmt=fmt@entry=0x7ffff7f55943 "*** %s ***: terminated\n")
at ../sysdeps/posix/libc_fatal.c:155
#6 0x00007ffff7eb076a in __GI___fortify_fail (msg=msg@entry=0x7ffff7f5592b "stack smashing detected") at ./debug/fortify_fail.c:26
#7 0x00007ffff7eb0736 in __stack_chk_fail () at ./debug/stack_chk_fail.c:24
#8 0x00005555555551c1 in main () at erreurs.c:13
(gdb)
On constate que l’erreur se situe à la ligne 13, à la fin de la fonction main
. La prochaine étape consiste à ajouter un point d’arrêt (breakpoint) dans cette fonction, par exemple à la ligne où la variable tableau
est affectée.
Pour arrêter le programme exactement à la ligne 8 (par exemple) du fichier erreurs.c
, vous pouvez placer un point d’arrêt manuellement dans l’interface interactive de GDB.
(gdb) break erreurs.c:10
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Le programme s’exécutera jusqu’à atteindre le point d’arrêt défini.
Cela vous permet d’examiner l’état du programme, notamment les valeurs des variables à ce moment précis, et de diagnostiquer plus facilement les erreurs éventuelles.
Après avoir placé le point d’arrêt et que le programme s’est arrêté, vous pouvez :
tableau
, utilisez :
print tableau
ou
p tableau
Cela affichera la valeur de tableau
(ou son pointeur).
p tableau[0]@5
Ceci affichera les 5 premiers éléments à partir de l’indice 0.
n
(next) :
n
La commande n
exécute l’instruction courante et s’arrête à la suivante. Cela permet de progresser étape par étape dans le programme, tout en restant dans la fonction courante.
continue
Ainsi, vous pouvez inspecter les variables, avancer dans le programme à l’aide de n
, et ajuster vos points d’arrêt si nécessaire.
L’objectif principal de l’utilisation de la commande (gdb) break
est d’arrêter l’exécution d’un programme à un point précis afin de permettre l’inspection et la modification du contexte actuel avant de poursuivre son exécution. Cela facilite considérablement le débogage et l’analyse des programmes.
break
dans GDB :Ajouter un point d’arrêt à une ligne spécifique :
Si vous souhaitez arrêter votre programme exactement à la ligne 10 du fichier monprogramme.c
, vous pouvez définir un point d’arrêt en utilisant la commande suivante dans l’interface interactive GDB :
(gdb) break monprogramme.c:10
Cela interrompra l’exécution du programme lorsqu’il atteindra la ligne 10.
Ajouter un point d’arrêt à une fonction spécifique :
Si vous souhaitez arrêter l’exécution à chaque fois que le programme entre dans la fonction ma_fonction
, vous pouvez définir un point d’arrêt avec la commande :
(gdb) break ma_fonction
Le programme s’arrêtera à chaque fois que la fonction ma_fonction
est appelée.
La commande break
est essentielle dans le processus de débogage avec GDB, car elle permet de geler l’exécution du programme à des moments clés. Cela vous donne l’opportunité d’inspecter les variables, de tester des hypothèses sur l’état du programme, et même de modifier certaines valeurs avant de poursuivre l’exécution. Cette intervention proactive vous aide à mieux comprendre les événements critiques qui surviennent dans votre programme.
Pour quitter GDB, vous pouvez utiliser la commande suivante :
quit
Si le programme en cours de débogage s’exécute encore, GDB vous demandera confirmation pour quitter. Il vous suffira de taper y
(yes) ou d’appuyer sur Entrée pour confirmer et sortir de l’interface GDB.
(gdb) quit
A debugging session is active.
Inferior 1 [process 3886] will be killed.
Quit anyway? (y or n) y
L’objectif est d’apprendre à utiliser le débogueur Python pas à pas pour analyser et corriger une erreur dans le code.
from typing import List
import pdb
def double_values(tableau: List[int]) -> None:
for compteur in range(len(tableau)):
# Introduire une erreur : parfois multiplier par 3 au lieu de 2
if compteur % 2 == 0:
tableau[compteur] *= 2
else:
tableau[compteur] *= 3
# Insérer un point d'arrêt pour observer la valeur de 'tableau' à chaque étape
pdb.set_trace()
return tableau
# Créer un tableau d'exemple
tableau: List[int] = [1, 2, 3, 4, 5]
# Appeler la fonction et stocker le résultat
resultat = double_values(tableau)
# Afficher le résultat final
print(resultat)
pdb.set_trace()
), utilisez les commandes suivantes pour examiner l’état du programme :
n
pour passer à la ligne suivante.p tableau
pour afficher le contenu du tableau à chaque étape.p compteur
pour afficher la valeur actuelle du compteur.from typing import List
def double_values(tableau: List[int]) -> None:
for compteur in range(len(tableau)):
# Introduire une erreur : parfois multiplier par 3 au lieu de 2
if compteur % 2 == 0:
tableau[compteur] *= 2
else:
tableau[compteur] *= 3
return tableau
# Créer un tableau d'exemple
tableau: List[int] = [1, 2, 3, 4, 5]
# Appeler la fonction et stocker le résultat
resultat = double_values(tableau)
# Afficher le résultat final
print(resultat)
exercice_pdb.py
).python3 -m pdb exercice_pdb.py
l
: pour lister les lignes de code autour de la ligne actuelle.n
: pour passer à la ligne suivante.b <numéro_ligne>
: pour placer un point d’arrêt (breakpoint) à une ligne spécifique (par exemple, b 5
pour placer un point d’arrêt sur la ligne 5).c
: pour continuer l’exécution jusqu’au prochain point d’arrêt.p tableau
: pour afficher le contenu du tableau à tout moment.p compteur
: pour voir la valeur actuelle de compteur
.Veuillez noter toutes vos observations directement dans les fichiers C (erreurs.c
) et Python (erreurs1.py
et exercice_pdb.py
) fournis, sous forme de commentaires.
Voici les étapes de la chaîne de compilation :
Préprocesseur : Le préprocesseur gère les directives comme #include
pour insérer des bibliothèques et des macros.
Compilation : Le compilateur transforme le code en assembleur et optimise les performances.
Edition de liens : L’éditeur de liens combine les fichiers objets et bibliothèques nécessaires.
Exécution : Enfin, le programme binaire est exécuté.
// Directive de préprocesseur pour inclure la bibliothèque mathématique
#include <math.h> // Pour utiliser la constante M_PI
#include <stdio.h>
// Fonction pour calculer l'aire d'un cercle
float calculer_aire(float rayon) {
// Utilisation de la constante pi de la bibliothèque mathématique
return M_PI * rayon * rayon;
}
// Point d'entrée du programme
int main() {
float rayon = 5.0;
float aire = calculer_aire(rayon);
// Affichage du résultat
printf("L'aire du cercle de rayon %.2f est %.2f\n", rayon, aire);
return 0;
}
gcc -E aire.c -o aire.i
gcc -O2 -S aire.i -o aire.s
gcc -c aire.s -o aire.o
gcc aire.o -lm -o aire
./aire
L’option -O2
active des optimisations standard qui améliorent la performance sans compromettre la taille du code ou la compatibilité.
Un programme C qui calcule le volume et la surface d’une sphère en utilisant deux fichiers source distincts : un pour le calcul du volume et un autre pour la surface.
gcc -E main.c -o main.i
gcc -E volume.c -o volume.i
gcc -E surface.c -o surface.i
gcc -O3 -c main.i -o main.o
gcc -O3 -c volume.i -o volume.o
gcc -O3 -c surface.i -o surface.o
gcc main.o volume.o surface.o -lm -o sphere
./sphere
L’option -O3
active des optimisations plus agressives que -O2
, telles que l’optimisation des boucles, l’inlining de fonctions, et d’autres techniques qui peuvent améliorer la performance dans les calculs intensifs comme ceux de ce programme.
Veuillez noter toutes vos observations directement dans les fichiers C fournis, sous forme de commentaires.
Apprendre à analyser le bytecode d’une fonction Python avec le module dis
et comprendre comment l’interpréteur Python exécute les instructions.
Dans cet exercice, vous allez utiliser le module dis
pour examiner le bytecode d’une fonction Python et répondre à des questions précises sur le processus d’interprétation. Le but est de comprendre comment Python traduit des fonctions en instructions bas-niveau et de découvrir les optimisations internes.
dis
?calculer_aire
en simplifiant l’expression mathématique, par exemple, en remplaçant math.pi * rayon * rayon
par une valeur constante. Exécutez à nouveau dis
et comparez le bytecode généré. Quelles différences voyez-vous ?import math
import dis
def calculer_aire(rayon: float) -> float:
return math.pi * rayon * rayon
def main() -> None:
rayon : float = 5.0
aire : float = calculer_aire(rayon)
print(f"L'aire du cercle de rayon {rayon:.2f} est {aire:.2f}")
if __name__ == "__main__":
# Utilisation de dis pour afficher le bytecode de la fonction
dis.dis(calculer_aire)
main()
Que fait chaque instruction du bytecode ?
Par exemple, les instructions comme LOAD_GLOBAL
, LOAD_FAST
, ou BINARY_MULTIPLY
apparaîtront dans le bytecode. Que signifient-elles ?
LOAD_GLOBAL
: Cette instruction charge une variable globale (ici math.pi
).LOAD_FAST
: Charge une variable locale, comme rayon
.BINARY_MULTIPLY
: Effectue une multiplication binaire entre deux éléments du sommet de la pile.Modification de l’expression :
Si vous remplacez l’expression math.pi * rayon * rayon
par une valeur constante, quel changement observe-t-on dans le bytecode ? Est-il plus simple ou plus court ?
Comment fonctionne la pile d’exécution ?
Lors de l’analyse du bytecode, observez comment Python utilise une pile pour stocker temporairement les valeurs intermédiaires lors des calculs. Par exemple, les valeurs de rayon
et de math.pi
sont empilées, puis multipliées.
Ci-dessous un deuxième exemple utilisant le module dis
pour analyser le bytecode d’une autre fonction, cette fois-ci en introduisant une structure de contrôle comme une boucle for
. Cet exemple aidera à explorer le fonctionnement des boucles et des instructions associées dans le bytecode Python.
Analyser le bytecode d’une fonction Python contenant une boucle for
pour comprendre comment Python gère les structures de contrôle. Dans cet exercice, vous allez explorer comment le bytecode est généré pour une fonction qui utilise une boucle for
. Vous allez comparer la gestion de la boucle avec des opérations simples et découvrir comment l’interpréteur Python implémente ces structures de contrôle.
for
:
dis
pour observer le bytecode de la fonction calculer_somme
.SETUP_LOOP
, GET_ITER
, FOR_ITER
, et POP_BLOCK
.while
, et observez les différences dans le bytecode généré.import dis
def calculer_somme(n: int) -> int:
somme = 0
for i in range(n):
somme += i
return somme
def main() -> None:
n = 10
resultat = calculer_somme(n)
print(f"La somme des entiers de 0 à {n-1} est {resultat}")
if __name__ == "__main__":
# Utilisation de dis pour afficher le bytecode de la fonction
dis.dis(calculer_somme)
main()
for
dans le bytecode ?
GET_ITER
et FOR_ITER
. Que font-elles ?GET_ITER
: Cette instruction obtient un itérateur à partir de l’objet range(n)
.FOR_ITER
: Récupère le prochain élément de l’itérateur et gère l’arrêt de la boucle lorsqu’il n’y a plus d’éléments.SETUP_LOOP
et POP_BLOCK
. Quel est leur rôle dans la gestion de la boucle ?
SETUP_LOOP
: Prépare la gestion de la boucle et la gestion des exceptions (comme une interruption de la boucle).POP_BLOCK
: Enlève la boucle de la pile de contrôle une fois qu’elle est terminée.Modification de la boucle :
Changez la boucle for
pour une boucle while
et exécutez à nouveau le module dis
pour voir les différences dans le bytecode.
Exemple de modification :
def calculer_somme(n: int) -> int:
somme = 0
i = 0
while i < n:
somme += i
i += 1
return somme
Comparez les instructions générées pour la boucle while
avec celles de la boucle for
.
Veuillez noter toutes vos observations directement dans les fichiers Python fournis, sous forme de commentaires.
Manipulation de Chaînes de Caractères
Objectif : Écrire des programmes en C et en Python pour effectuer les opérations suivantes sur des chaînes de caractères, sans utiliser de bibliothèques standard ou externes :
\0
) et compter les caractères.\0
).\0
).Instructions :
En C : Écrire un programme nommé chaine.c
qui utilise les fonctions définies pour calculer la longueur, copier, et concaténer les chaînes. Demandez à l’utilisateur de saisir deux chaînes de caractères et affichez les résultats de chaque opération.
En Python : Écrire un programme nommé chaine.py
qui réalise les mêmes opérations que le programme en C. Demandez à l’utilisateur de saisir deux chaînes de caractères et affichez les résultats de chaque opération.
Gestion des données d’étudiant.e.s
Objectif : Écrire des programmes en C (bd_école.c
) et en Python (bd_école.py
) pour gérer les données de 5 étudiant.e.s en utilisant des structures en C et des classes en Python.
struct
) pour représenter les informations d’un étudiant. Cette structure doit inclure des membres pour le nom, le prénom, l’adresse, et les notes, par exemple.strcpy
pour initialiser les chaînes de caractères dans la structure.Exemple :
Un exemple d’utilisation de ce programme pourrait être :
Étudiant.e 1 :
Nom : Dupont
Prénom : Marie
Adresse : 20, Boulevard Niels Bohr, Lyon
Note 1 : 16.5
Note 2 : 12.1
Étudiant.e 2 :
Nom : Martin
Prénom : Pierre
Adresse : 22, Boulevard Niels Bohr, Lyon
Note 1 : 14.0
Note 2 : 14.1
...
Recherche de phrases dans un fichier
Objectif : Écrire des programmes en C (phrases.c
) et en Python (phrases.py
) qui permettent à l’utilisateur de rechercher une phrase spécifique dans un fichier donné. Le programme doit afficher les lignes du fichier où la phrase est présente, ainsi que le nombre de fois qu’elle apparaît dans chaque ligne.
fopen
, fgets
, et fclose
pour ouvrir le fichier et lire son contenu ligne par ligne.open
et readlines
pour ouvrir le fichier et lire son contenu ligne par ligne.Entrez le nom du fichier : fichier.txt
Entrez la phrase que vous souhaitez rechercher : exemple de phrase
Résultats de la recherche :
Ligne 10, 2 fois
Ligne 30, 1 fois
Gestion des couleurs et comptage des occurrences
Objectif : Écrire des programmes en C (couleurs.c
) et en Python (couleurs.py
) qui stockent un ensemble de couleurs et affichent les couleurs distinctes avec leur nombre d’occurrences. Chaque couleur est représentée par quatre valeurs : R (rouge), G (vert), B (bleu), et A (alpha), chacune étant un octet.
Couleur
pour représenter une couleur, avec des membres pour R, G, B et A.Couleur
pour stocker les couleurs.Couleur
pour représenter une couleur, avec des attributs pour R, G, B et A.Couleur
pour stocker les couleurs.ff 0x23 0x23 0x45
ff 0x00 0x23 0x12
ff 0x23 0x23 0x45
Le programme doit afficher :
ff 0x23 0x23 0x45 : 2
ff 0x00 0x23 0x12 : 1
Exercice : Synthèse des approches en langage C et Python
Objectif : Créer un rapport comparatif (programmation_C_Python.md
) détaillé qui synthétise les différentes approches de programmation en C et Python, en couvrant les aspects suivants :
Débogage : Compare les méthodes de débogage en C (avec gdb
) et en Python (avec pdb
), en expliquant les outils, les techniques et les différences dans l’approche du débogage.
Chaîne de Compilation : Décris le processus de compilation en C avec gcc
, en détaillant chaque étape (prétraitement, compilation, assemblage, et édition des liens) et compare-le avec la gestion des modules en Python.
Inspection de Code : Analyse l’utilisation de dis
en Python pour examiner le bytecode et compare-le avec les outils disponibles pour inspecter le code compilé en C.
Manipulation de Chaînes de Caractères : Compare les techniques de manipulation de chaînes en C (comme les fonctions de la bibliothèque standard) et en Python (comme les méthodes de chaîne et les f-strings), en mettant en évidence les avantages et les inconvénients de chaque approche.
Gestion des Données et Recherche dans les Fichiers : Compare la gestion des structures de données en C (structures) et en Python (classes) pour les données d’étudiant.e.s, et explique les techniques pour rechercher des phrases dans des fichiers en utilisant les deux langages.