3ETI, Examen [CSC2] Developpement Logiciel en C CPE Lyon
Transcription
3ETI, Examen [CSC2] Developpement Logiciel en C CPE Lyon
3ETI, Examen [CSC2] Developpement Logiciel en C CPE Lyon 2014-2015 (1ere session) durée 3h Tous documents et calculatrices autorisés. Répondez aux questions sur une copie séparée Le sujet comporte 15 pages Le temps approximatif ainsi que le barème sont indiqués pour les grandes parties. Notez que le barème est donné à titre purement indicatif et pourra être adapté par la suite. En cas de doute sur la compréhension de l’énoncé, explicitez ce que vous comprenez et poursuivez l’exercice dans cette logique. Note préliminaire: • Toutes les questions concernent le language de programmation C dans le cadre du developpement de logiciels sur architecture PC standard. • Nous vous invitons à commenter le code et les réponses. En particulier, en cas d’ambiguité de compréhension d’une question, ajoutez toutes remarques et illustrations supplémentaires permettant d’expliquer votre démarche. • Sauf mention contraire explicite, on supposera que l’on dispose d’un système Linux standard fonctionnant correctement sur un PC récent 64 bits (identiques aux conditions de TP des PC de CPE: int étant encodé sur 4 octets, pointeurs étant encodés respectivement sur 8 octets sur 64 bits). • On supposera que le code C est compilé avec une version récente de gcc sous la norme C99 (ou ultérieure) identique aux conditions de TP des PC de CPE. • On supposera dans chaque cas que les #include des en-tête standards nécessaires à la bonne compilation et exécution des programmes décrits sont correctement installés et appelés (ex. stdio, stdlib, string, math, etc). 1 3ETI [CSC2], 2014/2015, session 1 1 Méthodologie et bonne pratiques de programmation [50min max] 5 points On considère les structures suivantes #define NBR_PLANETE 8 //les noms des 8 planetes du systeme solaire. enum nom_planete {mercure,venus,terre,mars,jupiter,saturne,uranus,neptune}; struct info_planete { enum nom_planete nom; float rayon; int distance_soleil; int nbr_satellites; //la //le //la //le designation du nom d’une planete rayon moyen de la planete (float >0) distance moyenne au soleil (entier >0) nombre de satellites de la planete (entier >=0) }; //structure stockant les informations des 8 planetes du systeme solaire struct info_systeme_solaire { struct info_planete planete[NBR_PLANETE]; }; On suppose également que l’on dispose de la fonction systeme_solaire_initialiser qui vient initialiser une structure de type info_systeme_solaire avec les bons paramètres pour chaque planète. Notons que les planètes sont référencées dans l’ordre du tableau (mercure, venus, terre, mars, jupiter, saturne, uranus, neptune). On souhaite réaliser la fonction obtenir_info_planete qui prend en paramètre un pointeur vers un système solaire et un indice, et renvoie un pointeur vers la planète correspondant à cet indice. Par exemple, l’indice 0 correspondra à la planète mercure, et l’indice 3 à la planète mars. Le contrat de la fonction est le suivant: /* La fonction obtenir_info_planete permet de recuperer un pointeur vers * la planete du systeme solaire designee par son indice dans l’ordre * des planetes. * * Necessite: * - Pointeur vers un systeme_solaire non modifiable. Pointeur non NULL. * - Un indice entier designant la position de la planete dans l’ordre du systeme solaire. L’indice doit etre compris entre 0 et NBR_PLANETE-1 * (inclus). * Garantie: * - Renvoie un pointeur vers une structure d’info_planete non modifiable. Le pointeur est non NULL. * * */ Question 1 Écrivez l’en-tête de la fonction correspondant à ce contrat. const struct info_planete* obtenir_info_planete(const struct info_systeme_solaire *systeme,int indice_planete); 2/15 CPE Lyon 3ETI [CSC2], 2014/2015, session 1 Question 2 Écrivez l’implémentation du corps de cette fonction correspondant à ce contrat et à votre en-tête. const struct info_planete* obtenir_info_planete(const struct info_systeme_solaire *systeme,int indice_planete) { assert(systeme!=NULL); assert(indice_planete>=0); assert(indice_planete<NBR_PLANETE); const struct info_planete *planete=&systeme->planete[indice_planete]; assert(planete!=NULL); return planete; } On souhaite réaliser la fonction calculer_rayon_moyen qui, étant donné l’information d’une structure info_systeme_solaire, calcule le rayon moyen de l’ensemble des planètes. Le contrat et l’en-tête de cette fonction sont les suivants: /* Fonction calculer_rayon_moyen calcule le rayon moyen sur * l’ensemble des planetes decrites dans ce systeme. * * Necessite: * - Pointeur non modifiable vers un systeme_solaire. Pointeur non null. * - Un systeme pour lequel l’ensemble des planetes possedent un rayon > 0. Garantie: * * - Renvoie un flottant positif (>0) correspondant au rayon moyen de l’ ensemble des planetes. * */ float calculer_rayon_moyen(const struct info_systeme_solaire *systeme); Question 3 Écrivez le corps de cette fonction. Prenez soin de bien respecter les bons critères de programmation, et n’oubliez pas d’utiliser la fonction déjà existante. float calculer_rayon_moyen(const struct info_systeme_solaire *systeme) { assert(systeme!=NULL); int k=0; float rayon_moyen=0.0f; for(k=0;k<NBR_PLANETE;++k) { const struct info_planete* planete=obtenir_info_planete(systeme,k); float rayon=planete->rayon; assert(rayon>0); rayon_moyen += planete->rayon; } rayon_moyen/=NBR_PLANETE; assert(rayon_moyen>0); return rayon_moyen; } 3/15 CPE Lyon 3ETI [CSC2], 2014/2015, session 1 On souhaite finalement coder une fonction qui permet d’afficher sur la ligne de commande le nom (en toutes lettres) des planètes dont le rayon est compris entre deux bornes min et max. L’en-tête de cette fonction est la suivante: void afficher_planete_avec_rayon_specifique(const struct info_systeme_solaire *systeme,float rayon_min,float rayon_max) Question 4 Écrivez le contrat ainsi que le corps de cette fonction. Prenez soin de bien réspecter les critères de bonnes programmation. Notez qu’il est possible de créer des sous fonctions si cela permet de simplifier la lecture du code ou de factoriser celui-ci. 4/15 CPE Lyon 3ETI [CSC2], 2014/2015, session 1 const char* convertir_nom_planete(enum nom_planete nom) { assert(nom<NBR_PLANETE); switch(nom) { case mercure: return "mercure"; break; case venus: return "venus"; break; case terre: return "terre"; break; case mars: return "mars"; break; case jupiter: return "jupiter"; break; case saturne: return "saturne"; break; case uranus: return "uranus"; break; case neptune: return "neptune"; break; default: puts("Probleme"); abort(); }; } /* * afficher_planete_avec_rayon_specifique affiche le nom des planetes qui ont un rayon compris dans l’interval designe. * * Necessite: - Un pointeur vers une struct info_system_solaire non modifiable. * Pointeur non NULL. - Deux flottants positifs rayon_min et rayon_max tels que rayon_min<= * rayon_max. * Garantie: - Un affichage sur la sortie standard du nom des planete dont le rayon * est compris entre rayon_min et rayon_max. * */ void afficher_planete_avec_rayon_specifique(const struct info_systeme_solaire *systeme,float rayon_min,float rayon_max) { assert(systeme!=NULL); assert(rayon_min>0); assert(rayon_max>0); assert(rayon_min<=rayon_max); int k=0; for(k=0;k<NBR_PLANETE;++k) { const struct info_planete* planete=obtenir_info_planete(systeme,k); float rayon=planete->rayon; if(rayon>=rayon_min && rayon<=rayon_max) printf("%s\n",convertir_nom_planete(planete->nom)); } } 5/15 CPE Lyon 3ETI [CSC2], 2014/2015, session 1 2 Connaissances générales du développement logiciel [35min max] 4 points Question 5 Pour chacune des propositions suivantes, dites si celle-ci est vraie ou fausse en justifiant ou en expliquant votre réponse. (Une réponse sans explication n’apporte pas de points). Remarque: un apport d’informations supplémentaires pertinentes peut dans certains cas apporter un bonus. 1. Si on supprime les fichiers objets (.o), il n’est plus possible de lancer l’executable qui a été réalisé à partir de ceux-ci. 2. SVN est un compilateur C. 3. Un code sous licence GPL peut être modifié librement puis redistribué. 4. L’appel à la fonction sizeof sur un pointeur renvoie une taille qui dépend du type de la variable pointée. 5. Il est possible d’avoir des erreurs de gestion de mémoire lors de l’execution d’un programme alors que celui-ci termine normalement sans avoir affiché d’erreur de segmentation (ou Segmentation Fault en version anglaise). 6. Les commentaires (code après \\, ou entre \* et *\) sont parfois analysés par gcc pour optimiser le code binaire généré. 7. L’étape de préprocesseur lors de la compilation permet de générer le code assembleur associé au code C. 8. Pour comparer l’égalité de deux nombres flottants x1 et x2, il est préférable d’utiliser la comparaison suivante: if( x1-x2<epsilon ), avec epsilon initialisé à un nombre petit tel que 10−6 . 9. Dans un grand code déjà existant, il est généralement préférable de modifier l’interface des fonctions que leurs implémentations. 10. Soit une chaı̂ne de caractères contenue dans un tableau de 4 cases (char T[4]). Les 4 cases contiennent respectivement les lettres suivantes: abcd. L’affichage de ce tableau par la commande printf(‘‘%s’’,T) affiche de manière certaine abcd et uniquement cela. 11. La mémoire dite de cache permet de rendre inaccessible certaine variables au programme qui ne peut alors plus accéder à leurs valeurs respectives. On dit que cette mémoire vient les cacher ou encore les dissimuler. SOLUTION 1. Faux. Les fichiers objets sont uniquement nécessaire temporairement. L’exécutable contient le contenu de ces fichiers objets. Bonus: Au contraire, les librairies dynamiques sont encore nécessaire à l’exécution. 2. Faux. SVN est un gestionnaire de version, tout comme git. 3. Vrai. Le code redistribué doit également être sous liscence GPL. 4. Faux. Un pointeur est une adresse codé généralement sur 8 octets en 64 bits, et est indépendant de la taille de l’objet pointé. 6/15 CPE Lyon 3ETI [CSC2], 2014/2015, session 1 5. Vrai. L’erreur de segmentation correspond à un accès invalide détecté par le système d’exploitation. Il est possible d’avoir des erreurs mémoires et fuites mémoires qui ne sont pas détectés par le système d’exploitation et donne des erreurs dites silentieuses. Valgrind est un outil permettant de les détecter. 6. Faux. Le préprocesseur, première étape de la compilation, supprime les commentaires. 7. Faux. Le préprocesseur génère un code C plus simple à compiler, ce n’est pas de l’assembleur. 8. Faux. Il manque la valeur absolue entre x1 et x2 . 9. Faux. Modifier l’interface nécessite de modifier l’ensemble des fonctions qui l’utilise, alors que l’implémentation est locale (voir cours). Il est important de bien réfléchir au choix de son interface en amont qui a une influence globale. 10. Faux. Il n’y a pas la place pour stocker le caractère \0 dans le tableau, printf va peut être afficher le reste du contenu de la mémoire jusqu’à rencontrer un 0. 11. Faux. Il s’agit d’une mémoire tampon rapide proche du processeur permettant d’optimiser l’utilisation de la mémoire en évitant de trop nombreux accès en RAM ou sur le disque dur qui sont plus lents. 7/15 CPE Lyon 3ETI [CSC2], 2014/2015, session 1 3 Structs, adressage et portée des variables en C [20min max] 2 points Soit les structures, fonctions et code suivants (pour information, le programme compile et 5 lignes sont affichées à son exécution). 1 2 3 4 5 6 7 8 9 struct bouteille { int volume; int prix; }; struct collection { struct bouteille B[2]; }; int main() { struct collection c0; struct collection c1; 1 2 3 4 5 c0.B[0].volume=1; c1=c0; c1.B[0].volume=2; printf("%d\n",c0.B[0].volume); 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 7 8 9 10 void fonction_1(struct collection { c.B[0].prix=20; } void fonction_2(struct collection { struct bouteille b=c->B[1]; b.volume=4; } void fonction_3(struct collection { c->B[1].prix=6; } void fonction_4(struct collection { struct bouteille *b=&(c.B[1]); b->prix=9; } c) c0.B[0].prix=10; fonction_1(c0); printf("%d\n",c0.B[0].prix); c0.B[1].volume=3; fonction_2(&c0); printf("%d\n",c0.B[1].volume); 12 13 15 16 17 18 c0.B[1].prix=5; fonction_3(&c0); printf("%d\n",c0.B[1].prix); *c) 19 20 21 22 c0.B[1].prix=8; fonction_4(c0); printf("%d\n",c0.B[1].prix); c) 23 24 25 26 return 0; 27 } 28 SOLUTION // // // // // 11 14 *c) Question 6 Qu’affiche ce programme (écrivez votre réponse sur votre copie double)? Expliquez rapidement votre raisonnement pour chaque affichage (remarque: il est inutile de passer du temps à recopier le code du programme). 1 10 3 6 8 6 copie de la struct copie locale dans la fonction bouteille est une copie locale dans fonction_2 affectation directe sur c0 par adresse, pas de copie attention: pointeur local, ne modifie par c0 8/15 CPE Lyon 3ETI [CSC2], 2014/2015, session 1 4 Utilisation du mot clé const [20min max] 2 points Soit la structure et les 8 fonctions suivantes appelées depuis un programme (qui n’est pas décrit). 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 struct fleur_rose { int prix; int avoir_piquants; }; struct fleur_jasmin { int prix; int nbr_petales; }; struct bouquet { struct fleur_rose rose; struct fleur_jasmin jasmin; }; 1 2 3 4 5 6 7 void fonction_4() { struct bouquet b; initialize(&b); const struct fleur_rose *r=&(b. rose); } 18 19 20 21 22 23 void initialize(struct bouquet* b) { b->rose.prix=2; b->rose.avoir_piquants=0; b->jasmin.prix=3; b->jasmin.nbr_petales=9; } 24 25 26 27 28 29 void fonction_1() { struct bouquet b; initialize(&b); const struct bouquet b2=b; } 30 31 32 33 34 35 36 void fonction_2() { struct bouquet b; initialize(&b); const struct bouquet b2=b; const struct bouquet b3=b2; } 37 38 39 40 41 42 9 10 11 12 13 16 17 8 void fonction_3() { struct bouquet *b; initialize(b); struct bouquet b2=*b; } void fonction_5() { struct bouquet b; initialize(&b); const struct bouquet *b2=&b; const struct fleur_rose *r=&(b2-> rose); } 15 16 17 18 19 20 void fonction_6() { struct bouquet b; initialize(&b); const struct bouquet b2=b; struct fleur_rose *r=&(b2.rose); } 21 22 23 24 25 26 27 void fonction_7() { struct bouquet b; initialize(&b); const struct bouquet *b2=&b; struct fleur_rose *r=&(b2->rose); } 28 29 30 31 32 33 34 void fonction_8() { struct bouquet b; initialize(&b); const struct bouquet *b2=&b; struct fleur_rose r=b2->rose; } Question 7 Pour chacune des 8 fonctions, indiquez si celle-ci est correcte: c’est à dire ne présentant aucun warning ni erreur à la compilation et à l’exécution (Écrivez votre réponse sur votre copie. Il est inutile de passer du temps à recopier tout le code du programme). Si la fonction est incorrecte, expliquez pourquoi. SOLUTION 1. ok. Copie de struct vers const struct possible. 9/15 14 CPE Lyon 35 36 37 38 39 40 3ETI [CSC2], 2014/2015, session 1 2. ok. Idem avec const struct vers const struct. 3. NON. b pointe vers rien. Comportement indeterminé. 4. ok. Conversion adresse vers const adresse possible. 5. ok. Idem. 6. NON. Conversion d’un const vers adresse d’un objet modifiable (l.25) 7. NON. Idem (l.32) 8. ok. Copie d’une struct complete, la copie est modifiable et cela ne contredit pas le fait que l’objet originale était const. 10/15 CPE Lyon 3ETI [CSC2], 2014/2015, session 1 5 Bonnes pratiques de programmation et fichiers [30min max] 4 points Vous utilisez un code écrit en C qui gère les employés d’une entreprise. La structure entreprise contient au maximum NMAX employés. Ce code contient la fonction suivante dans son API: int calculer_cout_total_salaire(const struct employes T[NMAX]); Question 8 Que pensez vous de ce prototype de fonction? Vous semble t-il satisfaire aux contraintes de bonnes pratiques? Si non, quel serait votre proposition de changement? SOLUTION Mauvaise encapsulation. On montre explicitement que l’on stocke les employés dans un tableau statique (voir cours: code de haut niveau). Empêche de modifier localement l’implémentation sans avoir à modifier les codes appelants cette fonction partout dans le programme. On pourrait définir une structure qui encapsule le tableau. struct entreprise { struct employes T[NMAX]; }; La fonction devient alors int calculer_cout_total_salaire(const struct entreprise* E); Un employé de cette entreprise est enregistré en mémoire de manière à ce que l’on puisse stocker les informations suivantes: Son nom, age, fonction, salaire, montant de ses primes, ancienneté dans la société, numéro de sécurité sociale, nom de son superieur direct, nom des personnes sous sa responsabilité (maximum 8 personnes), nombre d’arrêts maladie dans l’année, nombre de jours de congés restants, sa photo, la couleur de ses yeux (marron, vert, ou bleu). On suppose que la photo est stockée dans un fichier présent sur le disque dur. On souhaite définir une ou plusieurs struct permettant de stocker et manipuler aisément l’enregistrement d’un employé. Question 9 Écrivez la définition de la (ou les) struct(s) C que vous mettriez en place afin de réaliser cet enregistrement. Faites en sorte de bien respecter les bonnes pratiques de programmation. SOLUTION Il est important de bien classifier les données dans différentes structures homogènes pour éviter d’avoir une seule struct qui contient tout. Par exemple, on pourrait considérer ce choix: 11/15 CPE Lyon 3ETI [CSC2], 2014/2015, session 1 #define NBR_MAX 256 #define MAX_RESPONSABLE 8 enum couleur_yeux {marron,vert,bleu}; struct identifiant_photo { char nom_fichier[NBR_MAX]; } struct individu { char nom[NBR_MAX]; int numero_secu; enum couleur_yeux yeux; struct identifiant_photo photo; } struct place_societe { char superieur[NBR_MAX]; char responsable[MAX_RESPONSABLE][NBR_MAX]; } struct payement { int salaire; int primes; } struct administratif { int nbr_arret_maladie; int jour_conges_restants; } struct employe { struct individu; struct place_societe; struct payement; struct administratif; } Question 10 Quelle fonction souhaiteriez vous écrire en lien avec cette struct sans même avoir à connaitre l’utilisation de cet enregistrement dans le code ? Ecrivez le prototype de cette fonction (inutile d’écrire l’implémentation de celle(s)-ci). Notez que vous n’êtes pas restreint à une seule fonction: Il est possible d’en définir plusieurs. SOLUTION On pourrait directement définir une fonction d’initialisation avec des valeurs par défaut. Egalement une fonction de vérification de l’integrité d’un employé, ou encore une fonction qui viens compléter les champs d’un employé. void employe_initialiser(struct employe* e); void employe_etre_integre(const struct employe *e); ... Vous souhaitez désormais écrire une fonction qui permet d’écrire dans un fichier texte sur le disque dur des informations d’identification d’une personne et de sa position dans l’entreprise. Voici un exemple du contenu d’un tel fichier pour une personne ayant sous sa responsabilité 3 autres employés: nom: Holtz Francois fonction: responsable 12/15 CPE Lyon 3ETI [CSC2], 2014/2015, session 1 age: 42 superieur: Botlz Michel responsable de : - Heitz René - Ruts Boris - Vickers Rolf La fonction d’écriture doit prendre comme paramètre le nom du fichier dans lequel elle doit écrire. Question 11 En prenant particulièrement soin de respecter les critères de bonnes pratiques vues en cours et en projet, écrivez la signature et l’implémentation correspondante de la fonction réalisant cette écriture dans un fichier pour une personne quelconque de l’entreprise. N’oubliez pas de donner un contrat à votre fonction. Réfléchissez aux paramètres de la fonction. Notez que vous avez le droit de faire des suppositions sur des valeurs standards des paramètres. Indiquez clairement les hypothèses que vous prenez. SOLUTION Critère: bien vérifier que le fichier est correctement ouvert, écrit et fermé à l’aide de if (et non d’assert uniquement). Il faut également se donner une hypothèse pour définir le nombre de responsable. on peut soit ajouter un champ dans la struct, soit supposer que l’on s’arrête lorsque l’on rencontre une chaine de caractère particulière (par exemple le nom 0). Penser à passer le nom de fichier en paramètre. Ecrire le code ... 13/15 CPE Lyon 3ETI [CSC2], 2014/2015, session 1 6 Compilation, Makefile et préprocesseur [25min max] 3 points On suppose que l’on dispose dans un même répertoire de 5 fichiers: Makefile, pgm_2.c, pgm_2.h, pgm_3.c, pgm_4.c dont les contenus sont décrits ci-dessous. pgm_2.h #ifndef PGM_2_H #define PGM_2_H #ifdef A #define VALEUR 6 #else #define VALEUR 9 #endif void fonction_2(); pgm_3.c #include <stdio.h> int main() { printf("Bonjour\n"); return 0; } #endif pgm_4.c pgm_2.c #include "pgm_2.h" #include <stdio.h> void fonction_2() { printf("%d\n",VALEUR); } #define A #include "pgm_2.h" #include <stdio.h> int main() { fonction_2(); printf("%d\n",VALEUR); return 0; } Makefile CFLAGS=-Wall -Wextra -g all: pgm_1 pgm_1: pgm_1.o pgm_2.o pgm_1.o: pgm_1.c pgm_2.h pgm_2.o: pgm_2.c pgm_2.h pgm_1.c: pgm_4.c cp pgm_4.c pgm_1.c clean: rm -f *.o pgm_1 pgm_1.c pgm_3 *˜ On suppose que l’on dispose d’une ligne de commande dans le répertoire où se situe l’ensemble des fichiers. Question 12 On tape make pgm_3. Que se passe t-il? Quels sont les fichiers désormais présents dans le répertoire après exécution de cette commande ? Question 13 On tape make. Que se passe t-il ? Quels sont les fichiers désormais présents dans le répertoire après exécution de cette commande ? Question 14 On tape ./pgm_1. Que se passe t-il ? Que voit-on affiché sur la ligne de commande ? 14/15 CPE Lyon 3ETI [CSC2], 2014/2015, session 1 1. make génère l’executable pgm_3 directement à partie de pgm_3.c. Il n’y pas pas besoin de déclaration dans le Makefile pour cela. 2. make cherche à générer pgm_1. Pour cela, il génère pgm2.o à partir de pgm2.c, génère pgm1.c en copiant pgm4.c, génère pgm1.o à partir de pgm1.c. Et fini par assembler pgm1.o et pgm2.o dans l’exécutable pgm1. 3. On affiche : 9 6 Ici fonction2 est exécutée à partir de pgm2.c qui a été compilé lorsque A n’est pas définie. Valeur est donc complété par 9 lors de la compilation. Le second affichage provient de pgm4.c qui viens afficher le contenu de Valeur après avoir déclaré A et inclu pgm2.h. Valeur vaut donc ici 6. 15/15 CPE Lyon