Le but de cet exercice (très) guidé est de comprendre les grandes lignes de la compilation séparé à travers un exemple.

  • Récupérer le code de jeu.c
  • Le compiler (et le lancer).
  • Le survoler. Quels genre d’éléments syntaxiques sont présents ?

Types, constantes, fonctions, variables globales, includes …

Premier découpage

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.

Second découpage

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 ?

Amélioration du découpage

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 variables static 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.

Pour le sport

  • 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 ?