IFT2030 Langages de programmation TP3 par: Nicola

publicité
IFT2030
Langages de programmation
TP3
par:
Nicola Grenon GREN30077303
et
Jean-François Delisle DELJ02057809
lundi dix-sept avril deux mille six
I – Le fonctionnement des programmes.
Les deux programmes que nous avons écrits sont basés sur un schème
similaire. Les principales différences structurelles, mis à part évidemment ce que
nécessite la syntaxe du langage, concernent surtout la structure de données sousjacente à la mémorisation des données. Là où le programme en C utilise des listes
simplement chaînées, le programme en Java utilise lui des LinkedList, simplement
parce que la structure était déjà disponible, simple d'utilisation tout en ayant une
similitude avec notre liste simplement chaînée intéressante. Mais au niveau des objets
Java ou des struct de C, la forme est identique.
Ainsi, la parcelle d'information de base dans les deux environnements est la
Chaîne définie comme contenant un string (au sens de suite de caractères) et un
compteur de multiplicité. Chaque fois qu'on voudra ajouter une chaîne de caractère à
notre base de données, on vérifiera donc si elle est déjà présente dans la liste de toutes
celles déjà employées et si c'est le cas, on incrémentera simplement le compteur au lieu
d'en allouer une nouvelle. De même, au moment d'en effacer une, on vérifiera si la
multiplicité de celle-ci est supérieure à 1 avant de l'effacer complètement.
Les individus de l'annuaire quant à eux se trouvent à n'être que des structures
vides contenant uniquement des pointeurs/références vers les chaînes précédemment
stockées. Il est à noter qu'on pourrait être tenté en Java de ne pas conserver de
compteur dans la structure de la chaîne puisque de toute façon tant que l'élément est
référencé il est conservé et dès qu'il ne l'est plus il est libéré. Or on s'aperçoit que parce
qu'on a besoin de conserver dans une liste (...) les éléments afin de savoir si on a
besoin de créer ou pas la nouvelle chaîne (ou d'en retrouver l'adresse mémoire) celle-ci
resterait donc indéfiniment active si on ne savait pas quand l'exclure justement de ladite
liste.
Le problème qu'il reste à prévoir en C surtout est la lecture de l'information à
partir de la console. Comme on ne sait pas à l'avance la longueur de la chaîne de
caractère qui sera reçue, on doit donc la lire petit à petit, en affectant, par petit bloc
d'espace, à nouveau dans une liste chaînée, les caractères lus. À la fin de la lecture
d'une chaîne, l'adresse de celle-ci sera finalement stockée dans la liste des struct
chaine.
Voici trois petits diagrammes précisant la façon dont sont gérées les données
dans notre programme C. La structure en Java est très proche pour les deux premiers,
mais emploie deux LinkedList plutôt que des listes simplement chaînées.
Structure de la liste des chaînes
liste
NULL
dummy
NULL
n
n
s
n: multiplicité
n
s
s: texte
s
Structure de l'annuaire des individus
annuaire
NULL
dummy
NULL
nom
num
rue
ville
nom
num
rue
ville
nom
num
rue
ville
Pointeurs vers des éléments de la liste des chaînes ...
Structure de la liste chaînée (blocs) employée pour la lecture du stdin
debut
NULL
c
c
c
c
c: *char
Individus
II – Comparaison des styles de programmation.
La principale différence au niveau de la programmation entre les deux langages
est bien entendu le haut niveau d'abstraction, permis par Java et les classes prédéfinies
accessibles, comparativement à C. Bien sûr en C nous aurions sans doute pu tirer
avantage de plus d'outils déjà prêts, mais il reste que dans l'ensemble, Java permet
souvent d'exprimer au moyen d'une simple instruction ce qui nécessite en C plusieurs
lignes de code.
L'encapsulation du Java permet également une synthétisation et un groupement
des informations qui rend la relecture du code beaucoup plus aisée.
La gestion simplifiée des exceptions est aussi un incontournable. Dans le code
du programme en C, le nombre de lignes que l'on est obligé d'ajouter pour gérer
simplement les dépassements de mémoire est effarant. À chaque lecture de la console,
on doit tester le retour, à chaque allocation d'un maillon de chaîne on doit tester le
retour et à chaque allocation d'un élément de nos chaînes on doit tester le retour... sans
compter les tests sur les retours eux-mêmes si on n'est pas déjà dans le main!
Et en elle-même, la gestion automatisée du collecteur d'espace mémoire inutilisé
(garbage collector) rend aussi caduque une bonne partie du code qui est nécessaire en
C.
2.1) Les chaînes de longueur quelconque.
Le problème de la lecture des chaînes de longueur quelconque en est un
du paradoxe de l'œuf ou de la poule... Pour savoir combien allouer de mémoire à
la structure de donnée qui contiendra ladite chaîne, il faudrait d'abord en
connaître la taille finale... ce qu'on ne saura qu'une fois la chaîne complètement
lue... et donc déjà stockée. Comme on est en l'absence d'une structure qui
s'ajuste automatiquement selon le besoin, on a donc élaboré une stratégie de
«morcelage». On a choisit de lire les sections de la chaîne petit bout par petit
bout en les concaténant ensuite via une liste chaînée. On perd un peu d'espace
dans le processus (pointeurs chaînant) et on a ensuite le choix entre traîner cette
structure complexe ou la transférer dans une seule grande chaîne de caractère
de la bonne longueur... nécessitant au passage des opérations coûteuses en
espace ou en temps de calcul.
La fonction Java elle permet de se libérer de cette complexité en faisant ce
genre de gestion de façon automatique.
2.2) Le partage des chaînes / la récupération de l'espace utilisé.
Notre solution au partage des chaînes utilise la même tactique dans les
deux cas. Nous en avons déjà discuté plus haut, mais il s'agit en résumé que la
structure ou l'objet définissant un individu (et donc ses informations) ne soit qu'un
registre de références à des éléments existants ailleurs, dans une liste qui elle
gère l'ensemble de toutes les chaînes connues et le compte du nombre de fois
que chacune est utilisée. Il ne nous reste alors plus qu'à incrémenter ou
décrémenter chacun des compteurs au besoin, en ajouter une nouvelle si elle
n'est pas déjà connue ou à libérer celles dont le compte descend à 0.
La récupération de l'espace, une fois cette structure établie, est donc
simple. Il suffit de libérer la mémoire au moment ou on exclu l'élément de notre
liste chaînée.
2.3) Le traitement des erreurs.
Le traitement des erreurs (donc des exceptions) est très fastidieux en C,
car il faut développer des automatismes. Pour le dépassement de mémoire par
exemple, il faut, à chaque malloc, prévoir un test pour déterminer si l'allocation a
réussi et prévoir un enchaînement d'actions à exécuter dans ce cas dans le
corps même de l'application. Tandis qu'en Java, le tout peut être traité en dehors
des limites des fonctions que nous tentons de définir et ainsi modulariser le
traitement des exceptions a part, de façon claire et concise.
Les autres types d'erreurs peuvent facilement être regroupées en Java et
aussi traitées de façon générale là où en C il faudrait essayer de prévoir tous els
cas de figure possibles et imaginables... Ce qui peut bien entendu être une
grosse source d'erreurs d'inattention, d'oublis et autres impondérables.
2.4) La syntaxe.
La syntaxe «âgée» de C en elle-même est également une source de
problèmes. Souvent mineurs, mais quand même agaçants, citons par exemple
l'obligation de déclarer avant le début du code toutes les variables qui seront
utilisées.
III – Forces et faiblesses.
Après avoir ainsi décrit les problèmes rencontrés, il devient facile de conclure
que le C a pour principale faiblesse, en ce qui nous concerne, une lourdeur
d'implémentation due à l'obligation, pour faire un code performant et sécuritaire,
d'ajouter un grand nombre de tests là où en Java on peut simplement prévoir le cas
principal et traiter de façon modulaire, à part, les cas d'exception. Il est
malheureusement très facile en C d'omettre des vérifications essentielles. De plus, le
modèle C ne nous permet pas aussi facilement de modulariser les structures de
données. On se trouve fréquemment, par exemple, à traiter dans le cœur même du
programme des éventualités à couvrir découlant de l'intérieur d'une des fonctions. En
Java, avec du bon code, quand on retourne au main, on n'a plus qu'à traiter
l'information qui y est acheminée, les cas spéciaux ont pour la plupart été réglés avant.
En Java par contre, on peut facilement perdre de vue la structure sous-jacente
des informations et par conséquent générer du code d'une moins grande efficacité voire
redondant ou inutile. Mais somme toutes, il est bien plus facile d'exprimer le même
concept en Java qu'en C.
Java supplante finalement clairement à notre avis C pour deux atouts qui sont
une réelle révélation, la gestion automatique de la récupération de la mémoire et la
gestion des exceptions. Deux outils qui nous ont fait sauver des heures et des heures
de travail.
IV – Hachage.
Il pourrait exister un problème en Java (et d'ailleurs en C) en ce qui a trait à
l'utilisation d'une table de hachage si cela nous faisait perdre de vue la gestion de la
multiplicité d'une chaîne rencontrée que nous avons utilisée. En l'occurrence, en Java,
si on utilisait le Hashtable disponible avec une vérification de collisions externes, on
perdrait l'avantage qu'on a dans notre structure plus simple, à savoir la désallocation
immédiate d'une chaîne qui n'est plus nécessaires. Dans la structure que nécessiterait
la Hashtable, nous perdrions cet avantage à moins d'une implantation complètement
manuelle (et coûteuse en temps de calcul) afin de ne plus référencer les chaînes
caduques dès qu'elle le deviennent.
Téléchargement