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