LdB Seance 3

De Ensiwiki
Aller à : navigation, rechercher
AttentionCette page est maintenue uniquement par les enseignants. Afin de ne pas perturber le déroulement des cours, elle n'a pas vocation à être modifiée par les élèves. Mais si vous avez des modifications à proposer, merci d'en discuter ou d'envoyer un e-mail aux auteurs de la page (cf. historique)

Laptop.png  Première Année  CDROM.png  Informatique 

Introduction

Le but de cette séance est de s'entrainer à écrire et manipuler des programmes simples en C.

Commencez par récupérer l'archive ZIP contenant les exemples à utiliser pendant cette séance. Vous pouvez la décompresser avec l'utilitaire unzip (si votre environnement graphique ne l'a pas déjà fait pour vous).

Premier pas en C

Dans l'éditeur de texte de votre choix, taper le court programme ci-dessous, dans un fichier hello.c :

#include <stdio.h>

int main(void)
{
    printf("Bonjour, quel age avez-vous ? ");
    int age;
    scanf("%i", &age);
    printf("Vous avez %i ans\n", age);
    return 0;
}

Compilez ce programme avec la ligne de commande :

gcc -o hello -m32 -std=c99 -g -Wall -Wextra hello.c

Puis exécutez le : ./hello

Exploration de la chaîne de compilation

Compilation

Il s'est en fait passé beaucoup de chose lors de l'exécution de la commande gcc. La première étape est ce qu'on appelle la compilation à proprement parler, qui consiste à traduire le code C en assembleur. On peut demander à gcc de ne réaliser que cette étape de compilation en utilisant l'option -S.

Exécutez la commande : gcc -S -m32 -std=c99 hello.c.

Cette commande génère un fichier hello.s qui contient du code assembleur Pentium. Ouvrez-le dans votre éditeur de texte, et essayez de retrouver les éléments suivants parmi le fouillis d'instructions et directives étranges qui apparaissent :

  • les définitions des chaînes de caractères utilisées dans le programme C initial
  • les appels aux fonctions printf et scanf, préfixés par une mystérieuse instruction call au nom évocateur
  • le nom de la fonction main suivie de deux points : vos souvenirs du cours d'architecture du premier semestre vous permettent de comprendre qu'il s'agit d'une étiquette.

Noter que le code des fonctions printf et scanf n'apparaît pas ce programme assembleur, car il s'agit de fonctions fournies dans le bibliothèque C standard. On verra plus loin comment elles sont effectivement utilisées.

Assemblage

Le programme assembleur généré par gcc -S décrit bien les instructions à exécuter mais sous forme textuelle pour nous en faciliter la lecture. Ce format n'est pas directement exécutable par le processeur, il faut donc traduire les instructions en binaire via une opération qu'on appelle l'assemblage.

Exécutez la commande : gcc -c -m32 hello.s

Cette commande génère un fichier hello.o qui contient le code binaire du programme. Ouvrez le avec votre éditeur de texte : à part quelques chaînes de caractères, son contenu est parfaitement illisible.

On peut utiliser la commande hexdump hello.o pour voir le contenu en hexadécimal du fichier, ce qui ne nous avance pas beaucoup plus (ajouter l'option -c à hexdump pour que l'utilitaire affiche les chaînes de caractères d'une façon un peu plus lisible).

La commande nm permet quant à elle d'afficher la liste des symboles (c'est à dire ici des noms de fonctions) définis ou utilisés dans le programme :

> nm hello.o
0000000000000000 T main
         U printf
         U scanf

Son exécution nous informe que la fonction main est définie dans la zone Text du programme (c'est à dire la zone contenant les instructions, par opposition par exemple à la zone Data contenant des données), et que les fonctions printf et scanf sont utilisées mais ne sont pas définies dans le programme (le U étant l'abréviation de Undefined).

Édition de lien

L'étape finale consiste à générer le programme hello qu'on pourra exécuter directement. On l'obtient en exécutant la commande : gcc -o hello -m32 hello.o.

On appelle cette étape l'édition de liens, car elle consiste précisément à lier le fichier objet hello.o avec tout ce dont il a besoin pour devenir exécutable, notamment :

  • le code de toutes les fonctions utilisées mais non-définies dans le programme hello.c (ici donc, le code de printf et scanf)
  • le code de démarrage permettant entre autres choses de passer à main les paramètres éventuellement fournis sur la ligne de commande.

Utilisation de gdb pour mettre au point des programmes

La mise au point de programmes écrits en C est beaucoup plus complexe que celle de programmes Ada. En Ada, le mécanisme des exceptions permet en général de trouver rapidement la ligne où le programme a échoué, et d'avoir une idée de l'erreur (e.g. raised CONSTRAINT_ERROR : prog.adb:4 range check failed nous dit bien que le programme prog a échoué à la ligne 4 lors d'un accès en dehors des bornes d'un tableau). En C, le même programme s'arrêtera avec comme seul message d'erreur le très sybillin segmentation fault...

Une technique basique pour mettre au point ses programmes consiste à rajouter des traces un peu partout, à coup de printf. Le principal problème de cette approche est qu'elle très inefficace car elle oblige à modifier et recompiler le programme pour ajouter les traces. Il existe heureusement des outils de mise au point dont le plus connu est gdb (GNU DeBugger).

Ouvrez avec votre éditeur de texte le fichier gdb-tutorial.c contenu dans l'archive que vous avez récupérée pour suivre pas à pas les manipulations à effectuer.

Si vous avez fini en avance

Ajoutez au fichier listes.c fourni à la séance 2 une fonction inverser_liste(struct cellule_t **l); qui inverse les éléments de la liste passée en argument. Vous aurez sûrement besoin de gdb pour la mettre au point, ainsi que de l'utilitaire Valgrind qui vous sera aussi utile pour le TPL1.