Le but de cet exercice (très) guidé est de comprendre les grandes lignes de la compilation séparé à travers un exemple.
Types, constantes, fonctions, variables globales, includes …
Nous allons commencer par séparer notre code en deux fichiers de code
source : jeu.c
et random.c
.
Créer random.c
vide (par exemple touch random.c
). Y déplacer le
code la fonction init_rand
.
Compiler random.c
en random.o
: cc -c -std=c99 random.c
.
Le -c
sert à demander une compilation
séparée et non un exécutable. On
peut remarquer que cette fois le -o
n’est plus nécessaire, puisque le
compilateur utilisera random.o
comme sortie par défaut. Le fichier
compile-t-il ? (je vous rappelle qu’un warning doit être considéré comme
une erreur). Rajouter les trois includes manquants et recompilez le —
stddef.h
est a rajouter du au -std=c99
. Cette fois le compilateur passe
silencieusement. A noter que jeu.c
n’a du coup plus besoin de time.h
, le
retirer.
Compiler jeu.c
en jeu.o
. Le fichier compile-t-il ?
Rajouter le prototype de
la fonction init_rand
dans jeu.c
.
Assemblez les morceaux (édition de liens). Construisez l’exécutable
monjeu
qui utilise jeu.o
et random.o
: cc -o monjeu jeu.o random.o
.
Exécuter le programme et constater qu’il marche toujours exactement comme
avant.
Après ce premier petit découpage, nous allons séparer la logique du jeu de celle des cartes. Certes ici le jeu se limite à distribuer qques cartes (mais bon).
Créer un fichier cartes.c
. Y déplacer affiche_paquet
. Compiler
cartes.c
. La compilation ne se passe pas bien, pourquoi ? Déplacez
tous les éléments nécessaires pour qu’il compile. (Bien que pas
nécessaire pour affiche_paquet
, je vous propose de déplacer de
suite les fonctions relatives à la manipulation du paquet)
Essayez de compiler jeu.c
. A nouveau la compilation
se passe très mal (pour exactement les mêmes raisons que juste
avant, vu que nous avons déplacé le problème).
Regardons un seul des warnings et une fois n’est pas coutume, pas
le premier mais celui relatif à melange_paquet
. Pour le faire
taire, il nous faut rajouter le prototype de la fonction
(melange_paquet
). Recompilez, et malheureusement il râle encore.
Pourquoi ? Essayons de rajouter l’équivalent des prototype pour les
structs
(ceci ce nomme type incomplet). Juste avant le prototype
de melange_paquet
rajoutez la ligne struct paquet;
(se prononce,
il existe un type struct paquet
). Recompilez, cette fois le
ce warning à disparu.
Nous allons tenter d’appliquer la même stratégie avec la fonction
nouveau_paquet
. Rajoutez son prototype (après la définition du
type incomplet cela va de soi). Ceci ne fait pas du tout taire le
message, pourquoi ? (indice: quel est la taille d’un type incomplet
?). Pas le choix, il nous faut compléter le type, i.e., ajouter
toute la définition et pas seulement le “il existe”. Copiez la
définition depuis cartes.c
. Ce n’est pas suffisant, qu’elles
définitions faudrait-il rajouter maintenant ?
Certes nous pourrions copier/coller toutes les définitions nécessaires mais cela ne semble pas une bonne idée. Pourquoi ? (a par: “car monsieur Morandat il dit que le copier/coller c’est mal”)
Pour éviter ce copier/coller, la solution utilisée consiste à créer
un fichier d’en-tête (cartes.h
) contenant toutes les définitions
utiles pour utiliser cartes.c
. Déplacez toutes les définitions de
types nécessaire ainsi que tous les prototypes dans cartes.h
et
incluez ce fichier dans jeu.c
et cartes.c
.
Pour éviter le problème d’inclusion circulaires (voir cours), pensez
à protéger votre fichier cartes.h
.
#ifndef _CARTES_H
# define _CARTES_H
// ... ici se retrouve tout le contenu du fichier carte.h
#endif
Notre exécutable est maintenant composé de trois parties : jeu.o
,
cartes.o
et random.o
. Compilez les bouts manquants. Faut-il
recompiler random.o
? Assemblez tous les bouts cc -o monjeu jeu.o
cartes.o random.o
.
Rajouter la constante MAX_CARDS
(52) et l’utiliser. Où doit elle
aller ? On veut rajouter MAX_PLAYER
(4). Ou doit elle aller ?
Maintenant que nous avons notre programme découpé, nous proposons de
l’améliorer un peu. Le travail se fera essentiellement sur cartes.c
Regardons les symboles exportés par cartes.o
, i.e., les noms des
variables et fonctions qui l’on pourra appeler depuis un autre
fichier source (aux includes près). La commande nm
sur un
fichier .o
permet de lister ces noms en plus de donner qques infos
(elle donne aussi les noms des symboles importés, ici printf
).
Parmi les symboles exportés on retrouve les deux tableaux
couleurs
et valeurs
. Ces tableaux ne devraient être
accessibles qu’au travers des fonctions texte_couleur
et
texte_valeur
. Rajoutez le mot clef static
devant leur
définition. Recompilez et constatez (avec nm
) qu’ils ont bien
disparus.
On peut se dire que chacun de ces tableaux ne devrait être visible que par sa fonction associé. Il est possible de déplacer les définitions
static
à l’intérieur de leur fonctions respectives et elles conserverons le même sens. Attention ces variables conservent leur état entre deux exécutions de la fonction, ce sont toujours des variables globales … sauf que leur nom ne sort pas de la fonction. J’aurais tendance a vous dire, évitez les variablesstatic
dans les fonctions pour le moment (qui peut être assez le long).
La fonction make_card
ne devrait pas être appelé de l’exterieur,
une fois qu’un paquet de carte est construit, on ne fait plus
apparaitre de carte ex-nihilo. Cette fonction est pourtant exporté
(cf. nm
). Retirez le prototype de carte.h
, recompilez. En
utilisant nm
vous constatez que le symbole est toujours présent.
Vérifiez, rajouter le prototype dans jeu.c
et appelez la fonction
par exemple affiche_carte(make_card(VALET, PIQUE))
dans votre
main
.
Retournez dans cartes.c
et préfixez la fonction de static
.
Recompilez cartes.c
, que constatez vous (avec nm
). Recompilez
jeu.c
, et … Il va de soit que le prototype d’une fonction
préfixé par static
ne doit jamais se retrouver dans un fichier
d’en-tête.
Regardez la fonction melange_paquet
. Que fait-elle (oui
elle mélange le paquet, je sais). La partie rand
semble obscure en première
lecture … voire en seconde aussi. Nous proposons de définir la macro
RAND_BTW
qui retourne un entier compris entre son premier paramètre et le
second (exclu). Cette macro est-elle sure ? bien écrite ? Ou faut-il la
déclarer ?
#define RAND_BTW(_x, _y) \
((rand()%((_y) - (_x))) + (_x))
Utilisez la macro dans melange
paquet et relisez le
code, est-il plus clair ?
Essayez de l’améliorer un peu, que proposez vous ? Allez y. Relisez le code l’algorithme est il plus clair ?