Programmer sous GNU/LINUX

Ce TP introductif à la programmation en C sous GNU/Linux vous familiarise avec la syntaxe du langage C, l'utilisation du compilateur gcc, le préprocesseur cpp, le débogueur, la compilation séparée avec make, ainsi que l'utilisation des bibliothèques. Il nécessite un environnement GNU/Linux avec gcc et les outils associés.

D'après le document Programmer sous GNU/LINUX

Cet article a été rédigé automatiquement à partir du document source, puis vérifié avant publication.

Programmer sous GNU/LINUX

Document source

Programmer sous GNU/LINUX

Programming, Math · PDF · 5 pages

Afficher l'aperçu du document

Consulter le document original →

Ce TP introductif à la programmation en C sous GNU/Linux vous familiarise avec la syntaxe du langage C, l'utilisation du compilateur gcc, le préprocesseur cpp, le débogueur, la compilation séparée avec make, ainsi que l'utilisation des bibliothèques. Il nécessite un environnement GNU/Linux avec gcc et les outils associés.

Objectifs

  • Écrire et compiler un programme C simple calculant la factorielle.
  • Utiliser un débogueur pour suivre l'exécution pas à pas.
  • Comprendre et appliquer la compilation conditionnelle avec le préprocesseur.
  • Mettre en œuvre la compilation séparée et utiliser make.
  • Créer et utiliser des bibliothèques statiques et dynamiques.
  • Découvrir d'autres outils d'analyse et de profilage du code C.

Prérequis et installation

  • Un système GNU/Linux avec gcc installé.
  • Connaissances de base en langage C.
  • Accès à un terminal et éditeur de texte.
  • Outils supplémentaires : débogueur (xxgdb ou ddd), make, lclint, gprof, nm.

Exercice 1 : Calcul de la factorielle

Écrivez un programme en C qui calcule la factorielle de 10 et affiche le résultat. Définissez la fonction suivante dans un fichier nommé factorielle.c :

unsigned int factorielle (unsigned int n)
{
  unsigned int i = 1, res = 1;

  while (i <= n)
    res = res * i++;
  return res;
}

Compilez ce programme avec la commande :

% gcc -g -Wall -Werror -ansi -pedantic -o factorielle factorielle.c

Les options utilisées signifient :

  • -ansi et -pedantic : respecter strictement la norme ANSI C.
  • -Wall : afficher tous les avertissements.
  • -Werror : traiter les avertissements comme des erreurs.
  • -g : inclure les informations pour le débogueur.
  • -o factorielle : nommer l'exécutable factorielle.

Si la compilation réussit, exécutez le programme :

% ./factorielle
3628800

Le résultat attendu est 3628800, la factorielle de 10.

Exercice 2 : Utilisation d'un débogueur

1. Placez un point d'arrêt avant l'appel à la fonction factorielle() dans la fonction main().

2. Lancez le programme en mode pas à pas (avec step pour entrer dans la fonction, next pour passer les appels).

3. Affichez les valeurs des variables locales res et i avec la commande display. Au début, ces variables ne sont pas initialisées et peuvent contenir n'importe quelle valeur.

4. Continuez l'exécution pas à pas pour observer l'évolution de res et i au fil des itérations de la boucle.

Relancez ensuite le programme avec un point d'arrêt identique et modifiez la valeur de n dans main() :

(xxgdb) set n=12
(xxgdb) cont

Le programme calcule alors la factorielle de 12. Recommencez avec n=13 et observez que le résultat ne correspond pas à 13 fois la factorielle de 12, en raison de la limite de 32 bits des entiers sur cette machine.

Exercice 3 : Compilation conditionnelle et préprocesseur

Le préprocesseur permet de sélectionner des portions de code à compiler selon des conditions. Par exemple :

#if ARCH = i386
/* code pour un intel i386 */
#endif
#if ARCH = alpha
/* code pour un DEC Alpha */
#endif

Question 1 : Renommez la fonction factorielle() en factorielle_iterative() et écrivez la fonction récursive :

unsigned int factorielle_recursive (unsigned int n);

Placez les deux versions dans une directive conditionnelle :

#ifdef RECURSIVE
unsigned int
factorielle_recursive (unsigned int n)
{
  ...
}

unsigned int
factorielle (unsigned int n)
{
  return factorielle_recursive (n);
}
#else
unsigned int
factorielle_iterative (unsigned int n)
{
  ...
}

unsigned int
factorielle(unsigned int n)
{
  return factorielle_iterative (n);
}
#endif

Question 2 : Définissez la macro RECURSIVE soit dans le fichier source avec #define RECURSIVE, soit lors de la compilation avec l'option -D RECURSIVE. Vérifiez le préprocessing avec :

% gcc -E -P factorielle.c | grep -v '^[ ]*$' | more
% gcc -E -P -D RECURSIVE factorielle.c | grep -v '^[ ]*$'

Vous verrez que la version compilée change selon la définition de la macro.

Question 3 : Compilez la version récursive et déboguez-la :

% gcc -D RECURSIVE -g -Wall -Werror -ansi -pedantic -o factorielle factorielle.c
% xxgdb factorielle

Placez un point d'arrêt avant le return de factorielle_recursive(), lancez l'exécution, puis avancez dans les appels récursifs avec step. Utilisez la commande bt pour afficher la pile d'appels et frame n pour naviguer dans un cadre d'appel particulier. Par exemple :

(xxgdb) print n
$1 = 4
(xxgdb) frame 3
#3  0x804840d in factorielle_recursive (n=7) at factorielle.c:5
(xxgdb) print n
$2 = 7

Exercice 4 : Compilation séparée et utilisation de make

Question 1 : Séparez le programme en deux fichiers sources :

  • factorielle.c : définitions des fonctions factorielle_iterative(), factorielle_recursive() et factorielle().
  • main.c : fonction main() uniquement.

Créez un fichier d'en-tête factorielle.h contenant :

extern unsigned int factorielle(unsigned int);

Incluez-le dans main.c :

#include <stdio.h>
#include "factorielle.h"
...

Compilez sans make avec :

% gcc -c -Wall -Werror -ansi -pedantic factorielle.c
% gcc -c -Wall -Werror -ansi -pedantic main.c
% gcc -Wall -Werror -ansi -pedantic -o factorielle factorielle.o main.o

L'option -c génère les fichiers objets sans édition de liens.

Question 2 : Créez un fichier Makefile avec le contenu :

CC      = gcc
CFLAGS  = -Wall -Werror -ansi -pedantic
CFLAGS  += -g

factorielle: factorielle.o main.o
        $(CC) $(CFLAGS) -o factorielle factorielle.o main.o
factorielle.o: factorielle.c
        $(CC) -c $(CFLAGS) factorielle.c
main.o: main.c
        $(CC) -c $(CFLAGS) main.c

Modifiez la date de factorielle.c et lancez :

% touch factorielle.c
% make

Seules les étapes nécessaires sont exécutées.

Question 3 : Ajoutez au Makefile les cibles clean et realclean :

.PHONY: clean realclean
clean:
        $(RM) factorielle.o main.o
realclean : clean
        $(RM) factorielle

Ces cibles permettent de supprimer les fichiers intermédiaires et l'exécutable.

Exercice 5 : Bibliothèques statique et dynamique

Question 1 : Modifiez le Makefile pour créer une bibliothèque statique libfact.a et l'utiliser dans le programme factorielle.

Question 2 : Modifiez le Makefile pour créer une bibliothèque dynamique libfact.so et l'utiliser dans le programme factorielle.

Exercice 6 : Autres outils

  • lclint : une version moderne de lint, analyse statique du code C, disponible sur le réseau enseignement. Essayez la commande lclint sur vos sources.
  • gprof : outil de profilage. Compilez avec -pg, exécutez le programme pour générer gmon.out, puis analysez avec gprof pour visualiser l'arbre des appels et le temps CPU passé dans chaque fonction.
  • nm : liste les symboles dans un fichier objet ou exécutable, permettant d'identifier variables et fonctions définies ou utilisées.

Résultats attendus

  • Affichage correct de la factorielle de 10 : 3628800.
  • Compilation sans erreurs ni warnings avec les options strictes.
  • Fonctionnement correct du débogueur, avec observation des variables locales et pile d'appels.
  • Commutation entre versions itérative et récursive via la macro RECURSIVE.
  • Compilation séparée fonctionnelle, avec recompilation partielle via make.
  • Création et utilisation réussies des bibliothèques statique et dynamique.

Pièges courants

  • Oublier un opérateur dans la boucle while (exemple : while (i n) au lieu de while (i <= n)).
  • Ignorer les warnings, qui sont traités comme erreurs avec -Werror.
  • Ne pas inclure correctement les fichiers d'en-tête, ou utiliser des guillemets au lieu des chevrons pour les fichiers locaux.
  • Ne pas définir la macro RECURSIVE pour tester la version récursive.
  • Confusion entre compilation complète et compilation séparée, oubli de l'option -c.
  • Ne pas utiliser les cibles clean et realclean dans le Makefile pour gérer les fichiers intermédiaires.
  • Limite de taille des entiers 32 bits, entraînant des résultats incorrects pour les factorielles supérieures à 12.

Partager

Commentaires

Aucun commentaire pour le moment. Posez la première question.

Les commentaires sont relus avant publication. Votre e-mail n'est jamais affiché.

← Toutes les révisions