Isup 1 - Programmation TD no4 Matthieu Journault 19 juin 2020
Exercice 1 : Rappel
Q. 1Ecrire une fonction r´´ ecursive qui calcule la mise `a la puissance n d’un entier m.
Solution /* Mise `a la puissance n de l'entier m */
int pow(int m, int n) { if (n == 0) {return 1;}
else {return (m * pow(m, n-1));}
}
Q. 2Ecrire une fonction r´´ ecursive calculant le binomial
n
m
Solution /* Calcul du coefficient binomial m parmis n */
int binomial(int m, int n) {
if (m == 0 || m == n) {return 1;}
else {return (binomial(m-1, n-1) + binomial(m, n-1));}
}
Q. 3Repr´esenter l’´etat de la m´emoire `a chacun des points de programme suivant (1, 2, . . .) void swap(int a, int b) { /* 4 */
int c = b ; /* 5 */
b = a ; /* 6 */
a = c ; /* 7 */
}
void incr(int u) { /* 9 */
u = u + 1; /* 10 */
}
int main() { /* 1 */
int u = 0 ; /* 2 */
int v = 3 ; /* 3 */
swap(u, v); /* 8 */
incr(v); /* 11 */
incr(v); /* 12 */
}
Solution 1. main
2. main u : 0
3.
u : 0 v : 3 main
4.
a : 0 b : 3 swap
u : 0 v : 3 main
5.
a : 0 b : 3 c : 3 swap
u : 0 v : 3 main
6.
a : 0 b : 0 c : 3 swap
u : 0 v : 3 main
7.
a : 3 b : 0 c : 3 swap
u : 0 v : 3 main
8.
u : 0 v : 3 main
9.
u : 3 incr
u : 0 v : 3 main
10.
u : 4 incr
u : 0 v : 3 main
u : 0 v : 3 main
9(2)
u : 3 incr
u : 0 v : 3 main
10(2)
u : 4 incr
u : 0 v : 3 main
12
u : 0 v : 3 main
Exercice 2 : Pointeurs
Q. 4Que remarquez vous concernant les modifications m´emoires dans la question 3 ? Comment expliquez vous ce ph´enom`ene ? Voyez vous un moyen permettant `a une fonction f appel´ee par une fonction g de modifier les variables locales deg.
Solution
Nous pouvons remarquer que les variables locales `a la fonction main ne sont pas modifi´es par les appels aux fonctions swap et incr. L’explication est simple : lors d’un appel de fonction en C, ce sont des valeurs qui sont propager vers la fonction appel´ee et non pas la variable. Ainsi dans l’exemple de la question 3, lorsque la fonctionswapmodifie le contenu deaetbles valeurs stock´ees dans les variables u et v ne sont pas modifi´es puisqu’une copie de ces valeurs a ´et´e donn´e `a la fonction swap. `A ce point d’avancement dans le cours il n’est donc pas possible de modifier les variables locales de la fonction appelante dans la fonction appel´ee.
Nous avons vu jusqu’`a maintenant plusieurs types de valeurs en C : les entiers, les flottants, les caract`eres. Il existe de nombreux autres types de valeurs en C, celui qui nous int´eresse particuli`erement aujourd’hui est le type depointeur.
La m´emoire est un grand bloc de “cases” telle que chaque case contient une valeur. Ces cases ont un emplacement dans la m´emoire, cet emplacement s’appellel’adresse de la case. L’adresse n’est en fait rien d’autre qu’un entier et la m´emoire peut ˆetre vu comme un tr`es grand tableau.
6 0
2 1
1 2
. . . 3 1498
2 1499
1 1500
7 1501
6 1502
valeur
adresse . . .
En C il est possible de modifier le contenu de n’importe quelle case m´emoire, `a condition bien sˆur, d’en connaˆıtre l’adresse. Il sera donc souvent n´ecessaire en C de manipuler des adresses des cases de la
m´emoire. Toutefois afin de ne pas introduire de confusions pour le programmeur, le langage C s´epare les entiers repr´esentant des entiers, des entiers repr´esentant des adresses. Ce dernier type de valeur est appelle un pointeur. Ainsi une valeur de type pointeur n’est rien d’autre que l’adresse d’une case m´emoire. Les pointeurs peuvent “pointer” vers diff´erents types de valeurs (le type de la valeur contenue dans la case point´ee par le pointeur), ainsi diff´erents types de pointeur existent et le type d’un pointeur pointant vers une valeur de type t est t *. Par exemple un pointeur vers une case m´emoire contenant un entier a le type int *, un pointeur vers une case m´emoire contenant un caract`ere a le type char
*. De la mˆeme mani`ere qu’il est possible de stocker un entier dans une case m´emoire il est possible de stocker un pointeur dans une case m´emoire (rappel : ce pointeur n’est en fait rien d’autre qu’un entier).
Q. 5 Quel est alors le type d’un pointeur pointant vers une case m´emoire contenant un pointeur vers un entier.
Solution int **
Voyons maintenant comment manipuler un pointeur. Si e est une expression de type pointeur (par exemple une variable contenant un pointeur), alors l’expression * e permet de lire (ou de modifier si `a gauche d’une affectation) le contenu de la case point´ee pare. Six est un ´el´ement ayant une adresse dans la m´emoire (par exemple une variable) alors &x est un pointeur vers cette adresse dans la m´emoire.
Q. 6Supposons que la m´emoire soit dans l’´etat suivant : p : @x
y : 4 f
x : 1
main (La fl`eche de p vers x repr´esente le fait que la valeur stock´ee dans la variable p est un pointeur vers la variable x). Repr´esenter la m´emoire apr`es l’ex´ecution de chacune des 3 instructions suivantes `a partir de cette ´etat.
1. *p = 3;
2. y = *p + 5;
3. *p = *p + 1;
4. p = &y; *p = 3;
5. *p = *(&y);
Solution
1.
p : @x y : 4 f
x : 3 main
2.
p : @x y : 6 f
x : 1 main
3.
p : @x y : 6 f
x : 1 main
4.
p : @x y : 4 f
x : 2 main
5.
p : @y y : 3 f
x : 1 main
6.
p : @x y : 4 f
x : 4 main
Q. 7Pour chacun des programme suivant donner les diff´erents ´etats de la m´emoire :
1. int x = 1; /* 1 */
int* y = &x; /* 2 */
int z = *y; /* 3 */
2. int* x; /* 1 */
x = &x; /* 2 */
int y = *x; /* 3 */
3. int x = 1; /* 1 */
int* y = &x; /* 2 */
int** z = &y; /* 3 */
int w = **z; /* 4 */
4. int* x; /* 1 */
x = &x; /* 2 */
int y = **** ((int****) x); /* 3 */
Solution 1. 1. main x : 1
2.
x : 1 y : @x main
3.
x : 1 y : @x z : 1 main
2. 1. main x : ? 2. main x : @x
3.
x : @x y : ? main
3. 1. main x : 1
2.
x : 1 y : @x main
3.
x : 1 y : @x z : @y main
4.
w : 1 x : 1 y : @x z : @y main
4. 1. main x : ? 2. main x : @x
3.
x : @x y : ? main
Le fait qu’une fonction modifie l’´etat de la m´emoire autre que ses propres variables locales est appel´ee uneffet de bord.
Q. 8 Ecrire une fonction´ incr dont vous pr´eciserez la signature et qui a pour effet d’incr´ementer la valeur stock´ee dans une variable. Donner un exemple d’utilisation d’une telle fonction, et repr´esenter les diff´erents ´etats de la m´emoire au long de l’ex´ecution de votre programme.
Solution /* Fonction d'incr´ementation */
void incr(int* p) { /* 3 */
*p = *p + 1; /* 4 */
}
/* Utilisation dans main */
int main() { /* 1 */
int x = 0; /* 2 */
incr(&x); /* 5 */
}
1. main
2. main x : 0
3.
p : @x incr
x : 0 main
4.
p : @x incr
x : 1 main
5. main x : 1
Q. 9D´efinir une fonctionswappermettant l’inversion du contenu de deux variables. Donner un exemple d’utilisation d’une telle fonction, et repr´esenter les diff´erents ´etats de la m´emoire au long de l’ex´ecution de votre programme.
Solution /* Fonction d'inversion */
void swap(int* p, int* q) { /* 4 */
int c = *p; /* 5 */
*p = *q; /* 6 */
*q = c; /* 7 */
}
/* Utilisation dans main */
int main() { /* 1 */
int x = 0; /* 2 */
int y = 3; /* 3 */
swap(&x, &y); /* 8 */
}
1. main
2. main x : 0
3.
x : 0 y : 3 main
4.
p : @x q : @y swap
x : 0 y : 3 main
5.
c : 0 p : @x q : @y swap
x : 0 y : 3 main
6.
c : 0 p : @x q : @y swap
x : 3 y : 3 main
7.
c : 0 p : @x q : @y swap
x : 3 y : 0 main
8.
x : 3 y : 0 main
Q. 10 On consid`ere la transformation lin´eaire f :
x
y
7→
x+y
x−y
. D´efinir une fonction f, dont vous pr´eciserez la signature, et appliquant cette transformation par effet de bord `a ses deux arguments. De mˆeme pour la fonction inverse de f.
Solution void f(float* x, float* y) {
float resx = *x + *y;
float resy = *x - *y;
*x = resx;
*y = resy;
}
void inverse_f(float* x, float* y) { float resx = (*x + *y) / 2 ;
float resy = (*x - *y) / 2 ;
*x = resx;
*y = resy;
}
Exercice 3 : Tri de 3 valeurs
Q. 11 D´efinir une fonction void tri2(int* x, int* y), qui place le minimum des valeurs point´ees par int* x et int* y dans la case m´emoire point´ee par int* x et le maximum dans la case m´emoire point´ee parint* y. Utiliser la fonction swap d´efinie pr´ec´edemment.
Solution void swap(int* p, int* q) { /* 4 */
int c = *p;
*p = *q;
*q = c; /* 5 */
}
void tri2(int* x, int* y) { /* 3 */
if (*x > *y) { swap(x, y);
} /* 6 */
}
Q. 12D´efinir une fonctionvoid tri3(int* x, int* y, int* z), qui place la plus petite valeur parmis les trois valeurs point´ees par int* x, int* y et int* z dans la case m´emoire point´ee par int* x, la deuxi`eme dans la case m´emoire point´ee par int* y et la plus grande dans celle point´ee par int* z.
Utiliser la fonction tri2.
Solution void tri3(int* x, int* y, int* z) { /* 2 */
tri2(x, y);
tri2(y, z);
tri2(x, y); /* 7 */
}
Q. 13 Tester votre programme en appelant tri3 sur des pointeurs pointant sur des cases m´emoires contenant 3, 2 et1 (dans cet ordre). Repr´esenter la m´emoire en entr´ee et en sortie de chaque fonction.
Solution void main() {
int x = 3;
int y = 2;
int z = 1; /* 1 */
tri3(&x, &y, &z); /* 8 */
}
1 :
x : 3 y : 2 z : 1 main
2 :
x : @x y : @y z : @z tri3
x : 3 y : 2 z : 1 main
3 :
x : @x y : @y tri2
x : @x y : @y z : @z tri3
x : 3 y : 2 z : 1 main
4 :
p : @x q : @y swap
x : @x y : @y tri2
x : @x y : @y z : @z tri3
x : 3 y : 2 z : 1 main
5 :
c : 3 p : @x q : @y swap
x : @x y : @y tri2
x : @x y : @y z : @z tri3
x : 2 y : 3 z : 1 main
6 :
x : @x y : @y tri2
x : @x y : @y z : @z tri3
x : 2 y : 3 z : 1 main
3(2) :
x : @y y : @z tri2
x : @x y : @y z : @z tri3
x : 2 y : 3 z : 1 main
4(2) :
p : @y q : @z swap
x : @y y : @z tri2
x : @x y : @y z : @z tri3
x : 2 y : 3 z : 1 main
5(2) :
c : 3 p : @y q : @z swap
x : @y y : @z tri2
x : @x y : @y z : @z tri3
x : 2 y : 1 z : 3 main
6(2) :
x : @y y : @z tri2
x : @x y : @y z : @z tri3
x : 2 y : 1 z : 3 main
3(3) :
x : @x y : @y tri2
x : @x y : @y z : @z tri3
x : 2 y : 1 z : 3 main
4(3) :
p : @x q : @y swap
x : @x y : @y tri2
x : @x y : @y z : @z tri3
x : 2 y : 1 z : 3 main
5(3) :
c : 2 p : @x q : @y swap
x : @x y : @y tri2
x : @x y : @y z : @z tri3
x : 1 y : 2 z : 3 main
6(3) :
x : @x y : @y tri2
x : @x y : @y z : @z tri3
x : 1 y : 2 z : 3 main
7 :
x : @x y : @y z : @z tri3
x : 1 y : 2 z : 3 main
8 :
x : 1 y : 2 z : 3 main
Exercice 4 : Pointeurs et pile d’appel
Q. 14Expliquer ce qui se passe lors de l’ex´ecution du programme suivant : int* f1(int* x, int* y) {
if (*x < *y) {return x;}
else {return y;}
}
int* f2(int* x, int* y) { int c;
else {c = *y;}
return (&c);
}
int main() { int x = 1;
int y = 2;
int* p = f1(&x, &y);
int* q = f2(&x, &y);
}
Solution
La premi`ere fonction f1 retourne un pointeur vers la case m´emoire x de la fonction main car celle-ci contient la valeur minimale parmi les cases m´emoires x et y. L’ex´ecution de la seconde fonction f2 produit une erreur car celle-ci retourne un pointeur vers une de ses variables locales, qui est d´esallou´ee `a la sortie de la fonction et donc le pointeur retourn´e n’est pas un pointeur valide.