Université Lille 1 - Sciences et technologies

Licence d’informatique
  2
e année
Module de BPC

TP d’introduction à la « pratique du langage C »

Gilles Grimaud     Philippe Marquet

Révision majeure, janvier 2018
version de janvier 2020

Ce document est disponible sous forme d’un fichier PDF.

Ce support sert à la fois pour les TDs et les TPs. Sauf mention contraire, chaque exercice est à préparer en TD, et ensuite à implémenter, compiler et tester en séance de TP.

Semaine 1: Expressions, types et fonctions (1/2)

1  Préalables - des outils

Nous allons utiliser quelques outils de base pour pouvoir écrire des programmes en langage C, les compiler, et les exécuter.

Vous aurez besoin d’etre à l’aise avec la ligne de commande Unix, et avec l’outil Git, que vous avez normalement vu au semestre précédent. Pour rappel, vous pouvez trouver les informations à ce sujet sur le portail:

https://www.fil.univ-lille1.fr/portail/index.php?dipl=L&sem=S3&ue=Unix&label=Documents

Cartes de référence   ...indispensable...

Une carte de référence, refcard en anglais, est un condensé de notes sur un sujet donné, un logiciel, etc. Des cartes de références pour les outils que nous allons utiliser sont disponibles. Des copies de ces cartes vous sont distribuées.

Durant ces travaux pratiques, les cartes de références suivantes vous seront particulièrement utiles :

Gardez ces cartes avec vous !

1.1  Un shell

Pour éditer, compiler et tester vos programmes, un shell qui s’exécute dans un terminal texte est très pratique. Nous utilisons le shell bash.

Rappelez-vous que :

La recherche de documentation est une des opérations essentielles que vous devez savoir faire avec un shell. Vous avez été aguerri à l’utilisation d’un shell dans le cadre du Stage Unix dispensé au semestre précédent.

Vous pouvez trouver la documentation de votre shell en tapant la commande man bash. De manière générale, vous pouvez trouver la documentation de commande en tapant man commande.

Souffleur

Comme vous le savez déjà la touche tabulation <→∣> peut vous aidez à souffler une commande dont vous avez saisi les premiers caractères. Cependant, il est aussi possible de l’utiliser pour souffler des paramètres pour la plupart des commandes standard.


Exercice 1
 (Manuel de la librairie standard)   Dans la suite de l’énoncé, nous utiliserons une fonction de la librairie standard du langage C. Il s’agit de la fonction putchar(). Les fonctions C de la librairie standard sont documentées dans le manuel du shell.



A FAIRE EN TD: Comment pouvez-vous consulter la documentation ?



A FAIRE EN TP: Testez ceci sur votre machine de TP.

1.2  Un éditeur, Emacs

Nous vous conseillons d’utiliser emacs comme éditeur de fichier texte. Cet éditeur vous permettra d’écrire vos programmes source en langage C. En pratique n’importe quel éditeur de fichiers textes convient. Cependant emacs prend en charge les fichiers sources écrits en langage C et rend leur lecture et leur édition plus agréables.

Il s’agit de vous assurer que vous disposez bien de emacs. En fait, il faut que vous disposiez d’une version au moins égale à Emacs 25.


Exercice 2
 (Recherche dans le manuel en ligne)  

A FAIRE EN TD:

Comment trouver dans l’aide en ligne l’option de la commande emacs qui permet de connaître le numéro de version de votre logiciel ? Plus particulièrement, comment chercher «version» dans les 417 lignes du manuel, relatif à emacs ?



A FAIRE EN TP:

Essayez cette manipulation sur votre machine de TP, et déterminez le numéro de versio de votre emacs

Consultez la carte de référence emacs pour prendre en main cet éditeur.


Exercice 3
 (C- et M-)  



A FAIRE EN TD:

Que signifient les notations <C-> et <M-> utilisées dans cette carte de référence ?

1.3  Une forge, GitLab

Tout au long du semestre, vous allez rendre vos travaux via GitLab. GitLab est une forge qui permet de gérer des dépôts Git.

Vous avez accès à un dépôt Git sur GitLab à l’adresse

https://gitlab-etu.fil.univ-lille1.fr/ls4-pdc/theme1

ce dépôt contient les éléments nécessaires à la réalisation du présent TP de BPC.

Vous ne pouvez pas directement travailler sur ce dépôt, vous allez devoir réaliser un fork. Un fork vous permet de copier ce dépôt, et d’obtenir votre propre copie sous forme d’un dépôt GitLab sur lequel vous pourrez travailler.


Exercice 4
 (Forker un dépôt GitLab)  



A FAIRE EN TP:

Authentifiez vous sur GitLab avec votre nom d’utilisateur et mot de passe du FIL, et accédez au dépôt

https://gitlab-etu.fil.univ-lille1.fr/ls4-pdc/theme1-g<i>-y<yy>

<i> étant votre numéro de groupe et <yy> l’année en cours, par exemple theme1-g7-y42 pour le groupe 7 en 2041/42.

Réalisez un fork de ce dépôt.

Il est nécessaire d’inviter les personnes qui vont devoir travailler sur ce dépôt via la fonction Members du menu Settings (se trouvant dans la barre de navigation de gauche sur la page du dépôt). Quand vous ajoutez une personne dans le dépôt, vous pouvez lui donner un rôle (par exemple développeur).


Exercice 5
 (Ajouter des personnes sur un dépôt)  

A FAIRE EN TP:

Ajoutez votre binôme et votre enseignant dans votre dépôt, en tant que développeurs.

Votre dépôt sur GitLab est maintenant prêt. Toutefois, ce dépôt est sur le serveur GitLab, c’est donc ce qu’on appelle un dépôt distant. Pour commencer à travailler, vous devez créer une copie locale (c’est-à-dire sur votre machine de TP) de ce dépôt distant. Cette action s’appelle cloner un dépôt.

Quelle est la différence entre un fork et un clone ?


Exercice 6
 (Cloner votre dépôt)  



A FAIRE EN TP:

Clonez votre dépôt GitLab vers le dossier de votre choix.

Attention : clonez bien votre dépôt (celui résultant du fork) et non pas le dépôt GitLab original. Depuis l’interface web de GitLab, vous pouvez lister vos dépôts via l’action de menu Projets Your Projects.

Une fois votre dépôt cloné, vous pouvez travailler dans le répertoire local en modifiant ou ajoutant des fichiers. Ensuite, pour valider ces modifications, vous devez réaliser un commit. Le commit permet d’enregistrer des modifications ou ajouts de fichiers dans le dépôt, de manière versionnée. Avant chaque commit, il faut indiquer à Git l’ensemble des fichiers modifiés (ou ajoutés).

Vous avez normalement vu l’utilisation de Git au premier semestre, cependant un bref rappel est peut-être nécessaire: si vous en avez besoin, vous pouvez passer un peu de temps à manipuler votre dépôt. Vous pouvez par exemple modifier et ajouter des fichiers, réaliser un commit et un push, puis demander à votre binome de récuperer vos modifications avec un pull.

1.4  Un compilateur

Nous vous conseillons d’utiliser gcc pour compiler vos programmes. Un compilateur de langage C est un programme qui lit un fichier texte contenant le code source contenant un programme écrit en C, et qui le traduit en un code exécutable qu’un microprocesseur peut exécuter. Le code exécutable par le microprocesseur est composé d’une suite d’octets qui doivent être compris comme autant d’instructions et de données à l’attention du microprocesseur. Il ne constitue donc pas un texte et n’est pas lisible par un humain.

Les microprocesseurs qui équipent vos machines ne savent exécuter qu’un seul type de code, que l’on appelle souvent code exécutable, code machine ou encore, de manière impropre code binaire. Le compilateur fait une fois pour toute l’opération de traduction. D’autres langages tel que Python font cette traduction à chaque exécution, on parle alors d’interpréteurs, et de langage interprété. Notez encore qu’il existe des langages hybrides qui tel que Java, qui recourent à un compilateur pour transformer le source dans une représentation intermédiaire aussi appelée bytecode. C’est alors une machine virtuelle qui termine, lors de chaque exécution, la traduction.


Exercice 7
 (Un compilateur, gcc)   La commande gcc transforme donc un code source en code exécutable.



A FAIRE EN TD:

En considérant que le code source est dans le fichier prog1.c et que vous souhaitez produire un programme executable dans prog1, quelles sont les options qu’il faut saisir pour compiler votre code source dans votre code exécutable ?

Ensuite, par quelle commande executer le fichier compilé?



A FAIRE EN TP:

Créez le fichier source suivant avec votre éditeur, puis compilez-le et executez le.

int putchar(int c);
int main() {
  putchar('H');
  putchar('i');
  putchar('\n');
}

Compilateurs C   ...en savoir plus...

Dans l’UE Pratique du C, nous utilisons gcc mais il existe bien d’autres compilateurs de code source en langage C vers du code exécutable. Au nombre des plus connus, on trouve : clang, icc (d’Intel) ou encore cl (de microsoft).

En général le compilateur produit un code exécutable pour le microprocesseur de l’ordinateur sur lequel le compilateur est exécuté. Cependant il est possible d’utiliser un compilateur pour produire un code exécutable destiné à un autre microprocesseur. Par exemple, on peut compiler un code C sur un PC/Linux (processeur intel) avec un compilateur arm-gcc qui produit un code exécutable pour processeur ARM (qui équipent notamment les smartphone). Dans ce cas le code exécutable produit par arm-gcc ne pourra pas être exécuté sur la machine qui l’a compilé, mais seulement sur le smartphone visé. On parle de cross-compilation.

Git ignore   ...en savoir plus...

Le dépôt ne doit contenir que les seuls fichiers «utiles».

Les fichiers de sauvegarde de votre éditeur, par exemple prog1.c~, ou les fichiers exécutables, par exemple a.out qui existe dans la copie locale du dépôt n’ont pas à être enregistrés dans le dépôt. Ils n’ont qu’un intérêt temporaires (les *~), ou peuvent être reconstruits (les exécutables par exemple).

On peut indiquer à Git d’ignorer ces fichiers en plaçant un fichier .gitignore à la racine du dépot.

Chaque ligne identifie un motif de noms de fichiers qui doivent être ignorés, par exemple :


bash$ cat .gitignore 
a.out
∗~

Ce fichier lui-même peut être (est) géré par Git !

On viendra compléter ce fichier au fur et à mesure.

2  Premiers pas en C

Il est d’usage d’écrire un programme «hello world». En voici une version un peu différente.


Exercice 8
 (Édition de code)  

A FAIRE EN TP: Avec emacs, ouvrez le fichier prog1.c présent dans votre dépôt git et dont les premières lignes sont
/* Ceci est un commentaire */

/**
 * Nous allons utiliser la fonction "putchar()" de la librairie 
 * standard du langage C pour sortir le caractère de code ASCII
 * "c" sur le terminal. Cette fonction retourne -1 en cas d'ereur.
 * Voici la déclaration de cette fonction :
 **/
extern int putchar (int c);

/**
 * Nous définissons maintenant la fonction "main()" : nous allons 
 * écrire le corps de cette fonction.
 * "main()" est la fonction qui sera exécutée au démarrage de 
 * notre programme.
 * Cette fonction ne prend pas de paramètre et retourne 0 si le
 * programme termine correctement (une autre valeur en cas d'erreur).
 **/
int
main()
  {
 putchar(72);
 ...

Notez que ce code choisit délibérément une indentation fantaisiste. Cette écriture peut sembler particulièrement dissonante pour des informaticiens habitués à programmer en Python. Cependant contrairement à Python le langage C n’impose pas d’indentation a priori. Ceci étant dit, une indentation fantaisiste complique la lecture d’un programme, aussi avec le temps, diverses stratégies d’indentations ont été proposés, les styles : K&R, BSD KNF, GNU... Consultez éventuellement la page https://en.wikipedia.org/wiki/Indentation_style.

Les éditeurs de code source proposent souvent des options de normaliser l’indentation d’un fichier ou d’une sélection de texte, automatiquement.


Exercice 9
 (Indentation)  

A FAIRE EN TP:

Trouvez dans la carte de référence emacs un moyen de reprendre l’indentation du code C que vous avez saisi. Reformatez ainsi tout le code source du fichier prog1.c.

Ensuite, réalisez un commit pour enregistrer cette modification dans votre dépôt Git. Ce commit devra être accompagné d’un message bref et pertinent, décrivant la modification réalisée (exemple: "correction de l’indentation").

De manière générale, vous devrez réaliser un commit pour chaque exercice en TP de PdC (même si l’énoncé de l’exercice ne le demande pas explicitement). Pensez également à faire un push à la fin de la séance.


Exercice 10
 (Mise en commentaires)  



A FAIRE EN TP:

Sachant que sous emacs la commande <M-;> est relative à la mise en commentaire, commentez la fonction main(), puis décommentez la.

La fonction putchar() prend en paramètre une valeur appelée c qui est déclarée de type int. Ce type int en C ne signifie pas exactement «entier». En fonction des caractéristiques du microprocesseur, une valeur N est choisie par le compilateur C1, et int signifie «mot mémoire de la machine pour manipuler des entiers dans l’intervalle [−2N..2N−1]».

Dans cet exemple de programme, observez que la fonction putchar() assume que la valeur de c sera un «code ASCII», donc une valeur comprise entre 0 et 255 qui est associée à un caractère alphanumérique. C’est bien ce caractère, et non la valeur entière utilisée pour le coder, qui sera affichée sur le terminal2 .

Dans la fonction main() figurent différentes manières d’exprimer une valeur littérale entière en C :

  1. 72 correspond à la valeur décimale 72 ;
  2. 0x69 représente la valeur hexadécimale 69, ce qui est équivalent à la valeur décimale 105 (6 × 16 + 9 = 105) ;
  3. ’!’ représente la valeur du code ASCII du caractère «!», soit la valeur décimale 33. Consultez éventuellement la page de manuel man ascii pour vous en convaincre.
  4. ’\n’ représente la valeur du code ASCII du caractère associé à line feed, retour à la ligne, ce qui est équivalent à la valeur décimale 10.

Exercice 11
 (Valeurs littérales et code ASCII)  



A FAIRE EN TD:

Quelle succession de quatre caractères l’exécution du programme principal va-t-elle afficher ?



A FAIRE EN TP:

Compilez et executez ce programme pour vérifier votre prédiction.


Exercice 12
 (Compilation)  



A FAIRE EN TD:

Quelle est la commande à utiliser pour lister les fichiers dans le réprtoire courant? Quelle est la commande à utiliser pour compiler prog1.c ?



A FAIRE EN TP:

En utilisant cette commande Assurez vous que votre fichier source est bien présent. Ensuite, compilez votre fichier source, et listez à nouveau les fichiers présents. Quel fichier l’exécution de gcc a créé ?


Exercice 13
 (Exécution)  



A FAIRE EN TD:

Comment lancer le fichier executable qui a été créé lors de l’exercice précédent?



A FAIRE EN TP:

Lancer l’exécution du fichier créé. Ajouter éventuellement ./ devant son nom, pour que bash le trouve.

Comparez l’affichage à la réponse que vous aviez fournis à l’exercice 11. Que pouvez-vous en dire ?

3  Exercices de préparation


Exercice 14
 (Type atomique et conversion implicite)  



A FAIRE EN TD:

En supposant faites les déclaration :

long int A = 256;
char B = 'A' ;

évaluer et donner le type des expressions suivantes :

    B+1          B+'1'        B+A
    B = 65       B == 65      B = 256 
    B++          ++B          B = 255

NOTE: Chaque expression est indépendante, c’est-à-dire que vous ne devez pas tenir compte des potentiels effets d’une expression lorsque vous considerez l’expression suivante



A FAIRE EN TP:

En TP, testez et vérifiez que vos prédictions sont correctes. Si vous vous rendez-compte d’une erreur, essayez de comprendre pourquoi cela arrive.

Pour tester votre prédiction, vous pouvez comparer l’expression de l’exercice avec une constante, par exemple vous pouvez vérifier que (A - 1) vaut bien 255 en faisant:

return (A - 1) == 255;

et en utilisant ensuite la commande shell echo $? pour vérifier la valeur de vérité de la comparaison (l’expression A - 1 est un exemple fictif qui n’apparait pas dans l’exercice, mais vous pouvez utiliser le même principe pour les expressions de l’exercice).


Exercice 15
 (Que fait ce programme?)   La fonction int putchar(int) prend en argument un code ASCII (un entier), et affiche sur la sortie standard le caractère correspondant à ce code ASCII. De plus, la fonction renvoie le code ASCII du caractère (en cas de succès), ou bien la valeur spéciale EOF en cas d’échec.
extern int putchar(int);
int foo(int x) {
    int a = putchar(x);
    return 42;
}

int main() {
    int b = foo(65); /* 65 est le code ascii de A */
}



A FAIRE EN TD:

Que fait ce programme ? Quelle est la valeur que recoit la variable a (en supposant que l’appel de putchar est un succes)? Meme question pour la variable b. i En supposant que le source soit dans le fichier prog.c, quelle est la ligne de commande shell à taper pour créer un fichier exécutable correspondant prog ?



A FAIRE EN TP:

Sur vos machines de TP, testez ce programme et vérifiez que le résultat est conforme à votre prédiction.

Pour vérifier que le programme fait bien ce que vous avez envisagé en TD, vous allez utiliser l’outil gdb, qui vous permettra d’exécuter pas à pas votre programme, et inspecter la valeur des variables à chaque étape.

Compiler un programme de manière à l’utiliser avec gdb

Pour utiliser gdb, vous devez passer les options O0 -g lors de la compilation et de l’édition de lien de votre programme. C’est à dire que pour compiler votre programme d’exemple, vous devrez taper gcc -O0 -o monProgramme monProgramme.c (en supposant que monProgramme.c est le nom de votre fichier source)

Executer votre programme avec gdb

Ensuite, pour exécuter votre programme avec gdb, au lieu de le lancer avec ./monProgramme, vous allez le lancer dans gdb avec la commande suivante: gdb ./monProgramme

Une fois ceci fait, vous obtiendrez l’invite de commande de gdb, qui est signalé par (gdb).

Une fois l’invite gdb obtenue, vous pouvez demander à gdb de commencer l’exécution du programme pas à pas, en commencant au début de main, pour ceci vous entrez dans gdb les deux commandes suivantes:

break main (ceci informe gdb qu’il faut suspendre le programme au début du main et d’entrer en mode pas-à-pas)

run (ceci demande à gdb de lancer votre programme... en tenant compte de la consigne précisée ci-dessus relative à l’arret sur le début de main bien entendu!)

Ensuite, vous pouvez exécuter votre programme pas à pas, tout en affichant à votre convenance le contenu de variables.

Quelques commandes utiles sous gdb

Vous aurez besoin de ces quelques commandes:


Exercice 16
 (Géométrie)  



A FAIRE EN TD:

Créer une fonction float square(float) qui permet de renvoyer le carré d’un nombre passé en paramètre.

Créer ensuite une fonction:

int is_within_distance(float x1, float y1, float x2, float y2, float dist)

qui utilise la fonction square, et qui renvoie 1 si la distance entre le point aux coordonnées (x1, y1) et le point aux coordonnées (x2, y2) est inférieur à dist (on renvoie 0 dans le cas contraire).

Créer ensuite un programme pour tester ces fonctions.



A FAIRE EN TP:

Compilez et testez ce programme sur votre machine de TP.

4  Projet: Différents sens pour les mêmes nombres

Comme vous l’avez vu en cours de codage, les systèmes informatiques ne traitent que des nombres, mais ces nombres (binaires) peuvent prendre des sens très différents selon le rôle que l’on veut leur faire jouer. La fonction de la librairie standard putchar() prend un int pour produire un caractère sur la sortie standard. Cependant d’autres affichages de cette même valeur «entière» peuvent être utiles. On peut par exemple vouloir afficher la suite de caractères entre ’0’ et ’9’ qui représente le nombre en décimal, ou en hexadécimal...

Dans la série d’exercices qui suit, il vous est demandé d’implémenter une série de fonctions. Implémentez ces fonctions dans un fichier numbers-test.c. Un squelette vous est fourni, avec une fonction int main() dans laquelle vous ferez des appels aux fonctions implémentées en vue d’en tester le bon fonctionnement.

Un Makefile vous est fourni, ce qui vous permettra de compiler votre fichier numbers-test.c en tapant simplement la commande make numbers-test.

Le Makefile est un fichier qui vous permet d’automatiser les tâches répétitives du développement (par exemple la compilation). De plus amples détails sur l’utilisation des Makefiles vous seront fournis plus tard.

4.1  Afficher des caractères


Exercice 17
 (Afficher un chiffre (décimal))  

A FAIRE EN TD:

Proposez une implémentation de la fonction de prototype

int put_digit(int digit);

qui affiche le chiffre (digit) d. Ainsi :

On utilisera la fonction de prototype int putchar(int) pour réaliser l’affichage.

La fonction put_digit renverra 0 en cas de succès, ou -1 en cas d’erreur (par exemple, si l’argument passé n’est pas compris entre 0 et 9, ou bien si l’appel de putchar échoue).

Créez ensuite un programme principal (fonction main) qui appelle cette fonction avec un chiffre de votre choix, ainsi qu’avec un argument invalide, et qui vérifie que la valeur de retour est bien celle attendue, dans les deux cas.



A FAIRE EN TP:

Implémentez la fonction dans le fichier numbers-test.c sur votre machine de TP, appelez la depuis la fonction main puis compilez et testez ceci.


Exercice 18
 (Afficher un chiffre hexadécimal)  

A FAIRE EN TD:

De même proposez une fonction int put_hdigit(int h); qui affiche un nombre h sous la forme d’un caractère hexadécimal. h est donc une valeur entre 0 et 15 et le code ASCII produit est donc :



A FAIRE EN TP:

Implémentez cette fonction, et testez la (en modifiant votre main de manière à l’appeler, puis en compilant et executant votre programme)

Les entiers en C   ...on fait le point...

Notez que comme int n’est pas un entier quelconque mais un mot mémoire d ne peut pas être «arbitrairement grand». Sur votre machine, N est défini à 32 donc int peut représenter 4.294.967.296 codes distincts. Les microprocesseurs utilisent le codage des nombres signés en «complément à deux» (cf. Cours de Codage, chapitre 1). Ce codage permet de représenter des nombres entre -2.147.483.648 et 2.147.483.647.

Cette asymétrie entre le plus petit et le plus grand int a notament comme conséquence qu’il n’est pas possible de calculer la valeur absolue du plus petit int.

4.2  Afficher des nombres

Les fonctions putchar(), put_digit() et put_hdigit() affichent un caractère correspond à la valeur int reçue en paramètre. La fonction putchar() permet d’afficher un caractère en donnant un int correspondant à son code ASCII (ou UTF-8), la fonction put_digit() affiche le chiffre (donc un caractère) correspondant à la valeur de type int donnée, etc.

Nous allons maintenant définir des fonctions qui considèrent le mot mémoire correspondant à un int (cf. l’encart Les entiers en C) comme une valeur entière qu’il conviendra d’écrire en décimal, hexadécimal, binaire, etc. Nous allons donc afficher des séries de caractères qui représentent les chiffres qui composent le nombre, éventuellement précédés d’un premier caractère pour indiquer le signe du nombre.

Semaine 2: Expressions, types et fonctions (2/2)


Exercice 19
 (Afficher un nombre - décimal)  

A FAIRE EN TD:

Donnez la définition de la fonction

int putdec(int d);

qui affiche les chiffres de l’écriture décimale de la valeur d, au besoin, précédé d’un signe négatif, et sans afficher de ’0’ inutiles à gauche du nombre.

Cette fonction retourne normalement 0, -1 en cas d’erreur.



A FAIRE EN TP:

Implémentez cette fonction, et testez la (en modifiant votre main de manière à l’appeler, puis en compilant et executant votre programme)

Nous avons vu que les opérations arithmétiques du langage C sur des valeurs du type int sont traduites en opérations élémentaires du microprocesseur. De plus, le microprocesseur travaille en «complément à deux». Enfin, le type int correspond à un mot mémoire de 32 bits. Ces éléments sont a prendre en compte pour comprendre comment est calculé l’opposé de -2.147.483.648.


Exercice 20
 (L’opposé de -2.147.483.648)  

Considerez la modification suivante à la fonction main() de numbers-test.c:

int main() 
{
    int i=-2147483648;
    putdec(-i);
    return 0;
}



A FAIRE EN TD:

A votre avis, que va afficher maintenant l’exécution de ce programme? Pourquoi? (Indice: détaillez les opérations réalisées sur entier 32 bits, depuis l’intialisation de la variable i, jusqu’au passage de l’argument à putdec)



A FAIRE EN TP:

Vérifiez que le comportement du programme est conforme à ce que vous avez prédit en TD.


Exercice 21
 (Afficher un nombre binaire)  



A FAIRE EN TD:

Proposez une fonction putbin(int b) qui affiche un nombre b en paramètre sous la forme d’une série de 0 et de 1.

Notez qu’il est possible de déterminer la valeur du bit de poids faible (ou de plusieurs bit) d’un nombre en utilisant l’opérateur &. Utilisez les opérateurs >> pour décaler les bits d’un nombre vers la droite...

Comme pour la fonction putdec(), il est inutile, d’afficher les 0 à gauche du nombre. Et comme pour putdec(), la fonction retourne 0 si le nombre a été correctement affiché sur la sortie standard, -1 sinon.



A FAIRE EN TP:

Implémentez votre fonction, et testez la (modifiant le main pour appeler votre fonction, puis en compilant et executant votre programme)


Exercice 22
 (Affichage hexadécimal)  

A FAIRE EN TD:

Proposez une fonction int puthex(int h) qui prend un nombre h en paramètre et qui produit sur la sortie standard une série de code ASCII représentant ce nombre h sous forme hexadécimale.

Pour vous aider, observez qu’un mot mémoire de 32 bits est composé de 8 quartets de 4 bits. Chaque quartet est un chiffre hexadécimal du nombre. Utilisez ici encore, avantageusement les opérateurs >> et &.



A FAIRE EN TP:

Implémentez votre fonction, et testez la (modifiant le main pour appeler votre fonction, puis en compilant et executant votre programme)

Réalisez maintenant une nouvelle version de la fonction main() de numbers-test.c :

int put_test_line(int n)
{
    putchar('t');
    putchar('e');
    putchar('s');
    putchar('t');
    putchar(' ');
    putchar('#');
    putdec(n);
    putchar(':');

    return 0;
}

int main()
{
    int i=-2147483648;
    put_test_line(1); putdec(214); newline();
    put_test_line(2); putdec(-74); newline();
    put_test_line(3); putdec(1); newline();
    put_test_line(4); putdec(-1); newline();
    put_test_line(5); putdec(0); newline();
    put_test_line(6); putdec(2147483647); newline();
    put_test_line(7); putdec(-2147483648); newline();
    put_test_line(8); putdec(-(-2147483648)); newline();
    put_test_line(9); puthex(0); newline();
    put_test_line(10); puthex(10); newline();
    put_test_line(11); puthex(16); newline();
    put_test_line(12); puthex(2147483647); newline();
    put_test_line(13); puthex(-2147483648); newline();
    put_test_line(14); puthex(0xCAFEBABE); newline();
    put_test_line(15); puthex(-1); newline();
    put_test_line(16); putbin(0); newline();
    put_test_line(17); putbin(1); newline();
    put_test_line(18); putbin(-1); newline();
    put_test_line(19); putbin(2147483647); newline();
    put_test_line(20); putbin(-2147483648); newline();

    return 0;
}

Vous trouverez ce code dans le fichier numbers-test.c du dépôt Git (en commentaire).


Exercice 23
 (Tester vos programmes)   Pour valider le bon fonctionnement de ce programme, nous vous donnons un fichier de trace correct que nos avons produit. Voyez numbers-out.txt du dépôt Git.

Vous aller utiliser la commande diff pour comparer la trace attendue par le programme précédent.



A FAIRE EN TD:

Quelle est la commande pour avoir le manuel de diff ?

Comment rediriger la sortie de votre programme vers un fichier?



A FAIRE EN TP:

Testez votre programme en comparant sa sortie avec le fichier fourni.

Si votre programme n’a pas le comportement attendu, c’est-à-dire si ce qu’il affiche diffère de la trace proposée... corrigez le !


Exercice 24
 (Test avec le Makefile)  

Vous allez modifier votre fichier Makefile pour qu’il permette de réaliser automatiquement les tests.

Un Makefile est composé d’un ensemble de règles. Chaque règle définit comment réaliser quelque chose (par exemple: produire un fichier exécutable, ou bien réaliser les tests).

Chaque possède 3 élements:

Par exemple, pour produire l’exécutable toto à partir du source toto.c, il faut taper la commande gcc -o toto toto.c. Donc, si on voulait créer une règle de Makefile pour automatiser cela, la cible serait toto, la commande serait gcc -o toto toto.c, et la dépendance serait toto.c (car il y a besoin que toto.c soit présent pour lancer le processus de compilation).

Les règles dans le Makefile sont formattées de la sorte:

maCible: maDependance1 maDependance2 MaDependance3 [... etc] commande shell 1 commande shell 2 [... etc]

Attention: l’indentation des commandes shell doit impérativement être faite par une tabulation, et non des espaces.

Vous pouvez avoir autant de règles que vous le souhaitez dans le Makefile. Lorsque vous tapez la commande make, c’est la première règle qui va être utilisée. Si vous voulez utiliser une autre règle, il faudra taper make <cible> (où <cible> est le nom de la cible de la règle que vous voulez utiliser).

Vous pouvez éditer le Makefile fourni pour compiler numbers-test.c et examiner la règle pour compiler numbers-test.c

Vous constaterez également qu’il existe une cible test dans le Makefile, mais celle ci n’est pas complète. Vous allez la compléter. Pour ceci, il va falloir déterminer les dépendances, et la commande à lancer pour réaliser le test.



A FAIRE EN TD:

Quelles sont les dépendances de la cible test ? (En d’autre mots, quels fichiers doivent être présents pour que les tests puissent être lancés?)

Pourquoi numbers-test.c n’est pas une dépendance de la cible test?

Quelle est la commande shell à lancer pour faire le test? (indice: vous l’avez déja lancée lors de l’exercice précédent)



A FAIRE EN TP:

Ouvrez votre Makefile avec un éditeur, et modifiez-le pour mettre en place ce test. Vérifiez que cela fonctionne en tapant make test


1
Selon les standards C, N doit être au moins égal à 16. En pratique, pour des raisons de compatibilité ascendantes, gcc, tel qu’il est utilisé dans Linux impose N=32 même lorsque le microprocesseur permettrait N=64.
2
Pour mémoire, la représentation des nombres dans les différentes bases et le codage ASCII ont été discutés dans le cours de codage. Voir http://www.fil.univ-lille1.fr/~salson/codage/Poly/poly.pdf accessible depuis le portail http://portail.fil.univ-lille1.fr/ls3/codage.

Ce document a été traduit de LATEX par HEVEA