c 2006 Marc Feeley IFT2030 page 490
Mode de passage de paramètre
•
Déf: mode de passage de paramètre = lien qui existe entre le paramètre formel et le paramètre actuel•
Normallement le choix du mode de passage se fait indépendamment pour chaque paramètre•
Les 4 modes les plus répandus1. par valeur (existe dans presque tous les langages) 2. par référence (Pascal, Modula-2, C++)
3. par valeur-résultat (Ada) 4. par nom (Algol, SIMULA)
c 2006 Marc Feeley IFT2030 page 491
R-value et L-value (1)
•
Déf: une expression “R-value” dénote une donnée tel qu’un nombre, enregistrement, pointeur, etc(c’est-à-dire l’évaluation d’une R-value produit une donnée)
•
Déf: Une expression “L-value” dénote unemplacement en mémoire (c’est-à-dire l’évaluation d’une L-value produit un emplacement)
c 2006 Marc Feeley IFT2030 page 492
R-value et L-value (2)
•
Exemple en C: dans cet énoncét[x] = t[y]+1;
t[x] est une L-value
x, y, t[y], 1 et t[y]+1 sont des R-value
•
Note: le nom vient de la position de ce typed’expression dans une affectation (du côté gauche
“L-value” ou du côté droit “R-value”)
c 2006 Marc Feeley IFT2030 page 493
Passage par valeur
•
Le paramètre actuel est une R-value•
Le paramètre formel est une variable créée pour l’activation et initialisée avec la valeur du paramètre actuel•
Exemple en Cint carre (int x) /* x pass´e par valeur */
{ x = x*x; return x; } void test ()
{ int n = 3;
printf ("%d", carre (n)); /* 9 */
printf ("%d", n); /* 3 */
printf ("%d", carre (n+1)); /* 16 */
}
n 3 x 3
c 2006 Marc Feeley IFT2030 page 494
Passage par référence (1)
•
Le paramètre actuel est une L-value, c’est-à-dire une variable ou tout autre emplacement mémoire•
Le paramètre formel désigne le même emplacement mémoire que celui désigné par le paramètre actuel au moment de l’activation (i.e. c’est un alias del’emplacement mémoire)
c 2006 Marc Feeley IFT2030 page 495
Passage par référence (2)
•
Exemple en C++int carre (int &x) /* x pass´e par r´ef´erence */
{ x = x*x; return x; } void test ()
{ int n = 3;
printf ("%d", carre (n)); /* 9 */
printf ("%d", n); /* 9 */
}
n 3 x
c 2006 Marc Feeley IFT2030 page 496
Passage par référence (3)
•
Le passage par valeur d’un pointeur permet d’obtenir le même effet en Cint carre (int *x) /* x = ptr. pass´e par valeur */
{ *x = (*x)*(*x); return *x; } void test ()
{ int n = 3;
printf ("%d", carre (&n)); /* 9 */
printf ("%d", n); /* 9 */
}
n 3 x
c 2006 Marc Feeley IFT2030 page 497
Passage par valeur-résultat (1)
•
Le paramètre actuel est une L-value, c’est-à-dire une variable ou tout autre emplacement mémoire•
Le paramètre formel est une variable créée pourl’activation et initialisée avec le contenu du paramètre actuel
•
Au retour de la procédure, le paramètre formel est copié dans le paramètre actuelc 2006 Marc Feeley IFT2030 page 498
Passage par valeur-résultat (2)
•
Exemple en Adai, n : integer;
procedure add2 (x : in out integer) is begin
x := x+i;
x := x+i;
end;
n := 0; i := 1;
add2 (n);
put (n); -- imprime 2 (serait 2 par r´ef´erence) add2 (i);
put (i); -- imprime 3 (serait 4 par r´ef´erence)
•
Permet accès plus rapide au paramètre formel que passage par référence (évite l’indirection)•
Utile pour les langages concurrents (effet externe à un moment précis)c 2006 Marc Feeley IFT2030 page 499
Passage par nom (1)
•
Le paramètre actuel est une L-value ou une R-value•
Une utilisation du paramètre formel évalue le paramètre actuel comme L-value (si du côté gauche d’uneaffectation) sinon comme R-value
•
Le paramètre actuel est évalué à chaque accès et dans le contexte de l’appelantc 2006 Marc Feeley IFT2030 page 500
Passage par nom (2)
•
Exemple en SIMULAinteger i, n;
integer array t(1:2);
procedure add2 (x); name x; integer x;
begin
x := x+1;
i := i+1;
x := x+1;
end;
n := 0; i := 1;
add2 (n);
outint (n,0); ! imprime 2 (serait 2 par r´ef´erence) ; n := 0; i := 1; t(1) := 0; t(2) := 0;
add2 (t(i));
outint (t(1),0); ! imprime 1 (serait 2 par r´ef´erence) ; outint (t(2),0); ! imprime 1 (serait 0 par r´ef´erence) ;
c 2006 Marc Feeley IFT2030 page 501
Passage par nom (3)
•
Les subtilités de la sémantique du passage par nom peuvent être exploitées pour créer des nouvellesstructures de contrôle
•
Exemple: calcul d’une somme générale, tel queX10
i=1
1
2i = 1
2 + 1
4 + . . . + 1
1024
c 2006 Marc Feeley IFT2030 page 502
Passage par nom (4)
real procedure somme (index, bas, haut, val);
name index, val;
integer index, bas, haut;
real val;
begin
integer j;
real s;
s := 0;
for j := bas step 1 until haut do begin
index := j;
s := s + val;
end;
somme := s;
end;
integer i;
outfix (somme (i,1,10,1/2**i), 10, 15); ! 0.9990234375 outfix (somme (i,1,10,i), 1, 4); ! 55.0 ;
c 2006 Marc Feeley IFT2030 page 503
Noms et portée (1)
•
Les noms sont utilisés pour identifier des types, variables, procédures, etc. (identificateurs)•
Dans la plupart des langages un même nom peut servir à désigner plusieurs chôses•
Les règles de portée d’un langage spécifient ce qui est désigné par un nom, en fonction du contexte de sonutilisation
c 2006 Marc Feeley IFT2030 page 504
Noms et portée (2)
•
Déf: une déclaration introduit un nouveau sens pour un nom• déclaration de type, de variable, de paramètre, de procédure, ...
• Exemple 1: int x; => déclaration de variable qui introduit le nom x
• Exemple 2: typedef signed char byte; =>
déclaration de type qui introduit le nom byte
• Exemple 3: void f (byte x) { ... } =>
déclaration de fonction et de paramètre qui introduit les noms f et x
c 2006 Marc Feeley IFT2030 page 505
Noms et portée (3)
•
Déf: la portée d’une déclaration = la région duprogramme où l’utilisation du nom déclaré désigne cette déclaration
•
Exemple en Cint carre (int x) { return x*x; } int f (short x)
{ return carre (carre (x/2)); }
Portée de “int x” = corps de carre, portée de “int carre (int x)” = corps de carre et f
c 2006 Marc Feeley IFT2030 page 506
Noms et portée (4)
•
La portée à un certain point du programme correspond à une fonction, p.e. dans le corps de carre:utilisation port´ee
d’un nom ---> d´eclaration
x int x
carre int carre (int x)
•
Les deux sortes de portée les plus répandues:1. Portée lexicale (plupart des langages)
2. Portée dynamique (Perl, APL, TeX, Lisp)
c 2006 Marc Feeley IFT2030 page 507
Portée lexicale (1)
•
Règle: un nom désigne la déclaration englobante la plus proche textuellement•
Donc, une déclaration locale à un bloc (ou procédure) a précéance, à l’intérieur de ce bloc, sur toute déclaration du même nom externe à ce blocc 2006 Marc Feeley IFT2030 page 508
Portée lexicale (2)
•
Exemple en Cchar *x = "allo"; /* d´eclaration 1 de x */
void proc1 (int x, int y)/* d´eclaration 2 de x */
{ if (x > y) {
int x = 2*y; /* d´eclaration 3 de x */
printf ("%d\n", x);/* x --> d´ecl 3 */
}
printf ("%d\n", x); /* x --> d´ecl 2 */
}
void proc2 () { proc1 (3, 2);
printf ("%s\n", x); /* x --> d´ecl 1 */
}
•
Note: en C, une “déclaration” de variable n’inclus pas l’initialisation (i.e. la portée de “int x” débute après le= dans “int x = x*y;”)
c 2006 Marc Feeley IFT2030 page 509
Portée lexicale (3)
•
Dans certains langages il est permis de déclarer une procédure dans une autre (procédures imbriquées), p.e. Pascal, Modula-2, Ada, SIMULA, Scheme, Perl mais pas C et FORTRAN•
La portée des déclarations locales à une procédure s’étend aux sous-procéduresc 2006 Marc Feeley IFT2030 page 510
Portée lexicale (4)
•
Exemple en Pascalprocedure A;
var x : char; (* variable locale de A *) procedure B; (* proc´edure locale de A *)
begin
writeln(x);
end;
procedure C; (* proc´edure locale de A *) var x : char; (* variable locale de C *) begin x := ’C’; B; end;
begin
x := ’A’;
B; (* imprime A *) C; (* imprime A *) B; (* imprime A *) end;
c 2006 Marc Feeley IFT2030 page 511
Portée dynamique (1)
•
Règle: un nom désigne la déclaration englobante la plus proche dans la chaîne d’activations•
Donc, si la procédure A appèle la procédure B, une déclaration qui est visible dans A le sera également dans B (à moins que B déclare le même nom)c 2006 Marc Feeley IFT2030 page 512
Portée dynamique (2)
•
Exemple en Perlsub A
{ local $x;
sub B
{ printf ("%s\n", $x);
} sub C
{ local $x = "C";
B;
}
$x = "A";
B; # imprime A C; # imprime C B; # imprime A }
•
Note: en Perl, l’utilisation de “my” à la place de “local” donne la portée lexicalec 2006 Marc Feeley IFT2030 page 513
Portée dynamique (3)
•
En général la portée dynamique est à éviter car sa sémantique dépend de l’ordre d’exécution•
Un cas où la portée dynamique est appropriée, c’est pour passer un paramètre implicite à toutes lesprocédures appelées pendant l’activation courante
c 2006 Marc Feeley IFT2030 page 514
Portée dynamique (4)
•
Exemple en C: changer la destination d’impressionvoid print_char (char c, FILE* output) { fputc (c, output); }
void print_str (char* msg, FILE* output) { while (*msg != ’\0’)
print_char (*msg++, output);
print_char (’\n’, output);
}
void print_page (char** t, int n, FILE* output) { int i;
for (i=0; i<n; i++)
print_str (t[i], output);
}
char* p[3] = { "ligne 1", "ligne 2", "ligne 3" };
void reports () { FILE* fich;
print_page (p, 3, stdout);
fich = fopen ("rapport", "w");
print_page (p, 3, fich);
fclose (fich);
}
c 2006 Marc Feeley IFT2030 page 515
Portée dynamique (5)
•
Si C utilisait la portée dynamique:void print_char (char c) { fputc (c, output); }
void print_str (char* msg)
{ while (*msg != ’\0’) print_char (*msg++);
print_char (’\n’);
}
void print_page (char** t, int n) { int i;
for (i=0; i<n; i++) print_str (t[i]);
}
char* p[3] = { "ligne 1", "ligne 2", "ligne 3" };
void reports () { FILE* output;
output = stdout;
print_page (p, 3);
output = fopen ("rapport", "w");
print_page (p, 3);
fclose (output);
}
c 2006 Marc Feeley IFT2030 page 516
Portée dynamique (6)
•
Dans un langage non-concurrent, on peut émuler la portée dynamique avec une variable globale qui est sauvegardée/restaurée à chaque point de liaison (dans un langage concurrent il y a un problème departage de la variable globale par plusieurs processus)
•
La portée lexicale se compile mieux (programmes plus rapides) que la portée dynamique car lacorrespondance nom–déclaration est connue à la compilation
c 2006 Marc Feeley IFT2030 page 517
Macros et portée dynamique (1)
•
Le compilateur C (cc) est en fait composé de plusieurs sous-programmes qui traitent à leur tour le programme à compilercpp ccom as
source en C source en
C épuré
code assembleur
code machine
cc
c 2006 Marc Feeley IFT2030 page 518
Macros et portée dynamique (2)
•
cpp = “préprocesseur C” qui traite toutes les directives de préprocesseur “#” et les macros (le résultat est un programme C sans directives et macros)• #include "...": inclusion de fichier
• #if/#ifdef ...: compilation conditionnelle
• #define ...: définition de macro
•
Déf: une macro c’est une sorte de procédure dontl’appel sera traité par le préprocesseur, en remplaçant l’appel par le corps de la macro avec une substitution textuelle des paramètres
c 2006 Marc Feeley IFT2030 page 519
Macros et portée dynamique (3)
Source Source apr`es pr´etraitement
#include <stdio.h> | int printf (char *format, ...);
| ... et le reste de
| /usr/include/stdio.h
#define N 10 |
#define MIN(x,y) (x<y)?x:y |
|
#if N < 5 |
int t[N]; |
#else |
int t[N*2]; | int t[10*2];
#endif |
|
int min3(int a,int b,int c)| int min3(int a,int b,int c) { a = MIN(a,b); | { a = (a<b)?a:b;
a = MIN(a,c); | a = (a<c)?a:c;
return a; | return a;
} | }
|
int main () | int main ()
{ | {
#ifdef sun |
printf ("allo SUN\n"); |
#endif |
|
#ifdef linux |
printf ("allo linux\n"); | printf ("allo linux\n");
#endif |
|
return 0; | return 0;
} | }
c 2006 Marc Feeley IFT2030 page 520
Macros et portée dynamique (4)
•
Les macros permettent d’éviter le coût d’activation (transferts de contrôle, copie des paramètres, ...)•
L’expansion textuelle des macros peut cependant accroître la taille de l’exécutable•
Note: une sorte de portée dynamique survient avec la substitution textuelle des paramètres lorsque la macro contient des déclarations•
Exemple#define swap(x,y) { int t = y; y = x; x = t; } void proc (int a, int b, int t, int t2)
{ swap(a,b); /* { int t = b; b = a; a = t; }; */
swap(t,t2); /* { int t = t2; t2 = t; t = t; }; */
/* le deuxieme appel ne change pas t et t2 */
}
c 2006 Marc Feeley IFT2030 page 521
Macros et procédures “en ligne”
(1)
•
Les macros ont d’autres différences sémantiques avec les procédures•
Catégorie syntaxique de l’expansion 6= appel (idem pour paramètres formels)#define abs(x) (x<0) ? -x : x
#define swap(x,y) { int t = y; y = x; x = t; }
b = abs(a+1)*2; /* b = (a+1<0) ? -a+1 : a+1*2; */
if (a < b) /* if (a < b) */
swap(a,b); /* { int t = b; b = a; a = t; }; */
else ... /* else ... <-- erreur de syntaxe */
#define abs(x) (((x)<0) ? -(x) : (x))
#define swap(x,y) \
do { int t = y; y = x; x = t; } while (0) b = (((a+1)<0) ? -(a+1) : (a+1))*2;
if (a < b)
do { int t = b; b = a; a = t; } while (0);
else ...
c 2006 Marc Feeley IFT2030 page 522
Macros et procédures “en ligne”
(2)
•
Évaluation multiple du paramètre actuel (ceci est un problème difficile à contourner en même temps que les conflits de noms)b = abs(f(a));
/* b = (((f(a)+1)<0) ? -(f(a)+1) : (f(a)+1))*2; */
swap(x[i++],x[j++]);
/* do
{ int t=x[j++]; x[j++]=x[i++]; x[i++]=t; } while (0);
*/
#define swap(x,y) \ do \
{ int *px=&(x), *py=&(y), t=*py; *py=*px; *px=t; } \ while (0)
c 2006 Marc Feeley IFT2030 page 523
Macros et procédures “en ligne”
(3)
•
Certains langages permettent d’indiquer qu’une procédure est “en ligne”•
Exemple en C++inline void swap (int *x, int *y) { int t = *y;
*y = *x;
*x = t;
}
•
C’est une indication qu’il est souhaitable que le corps de cette procédure soit expansé à la place de chaque appel•
Le compilateur est libre d’ignorer cette indication car le résultat du programme ne changera pasc 2006 Marc Feeley IFT2030 page 524
Macros et procédures “en ligne”
(4)
•
Le compilateur doit renommer les paramètresformels et variables locales de la procédure dans chaque expansion pour éviter les conflits de noms
int t, x;
swap(&t,&x); /* { int *v601 = &t;
int *v602 = &x;
int v603 = *v602;
*v602 = *v601;
*v601 = v603;
}
*/
c 2006 Marc Feeley IFT2030 page 525
Paramètres optionnels
•
Ada et C++ permettent de donner une valeur de défaut aux paramètres•
Si le paramètre actuel n’est pas fourni, la valeur de défaut est utilisée•
Exemple C++: affichage d’un entier dans une certaine base sur un certain fichier de sortievoid print (int n, int base = 10, FILE *f = stdout) { ... }
void proc ()
{ print (13); // decimal sur stdout print (13, 2); // binaire sur stdout FILE *fich = fopen ("xxx", "w");
print (13, 8, fich); // octal sur xxx fclose (fich);
}
c 2006 Marc Feeley IFT2030 page 526
Paramètres nommés
•
Ada et Lisp permettent de spécifier les paramètres actuels en les nommant (dans n’importe quel ordre)•
Utile lorsque combiné avec valeurs de défaut et aussi pour documenter le programme•
Exemple en Adaprocedure header (page : in Integer;
title : in String := "";
center: in Boolean := True) is begin ... end header;
procedure example is begin
header (100, "", False);
header (100, center => False);
header (center => False, page => 100);
end example;
c 2006 Marc Feeley IFT2030 page 527
Surdéfinition de procédure (1)
•
Ada, C++ et Prolog permettent d’utiliser un même nom pour déclarer plusieurs procédures (procéduressurdéfinies)
•
Le compilateur décide quelle procédure appeler en fonction du nombre et type des paramètres•
La déclaration qui concorde “le mieux” avec l’appel est choisiec 2006 Marc Feeley IFT2030 page 528
Surdéfinition de procédure (2)
•
Exemple en C++void print (int n) { ... } // V.1 void print (char c) { ... } // V.2 void test ()
{
print (123); // V.1 print (’X’); // V.2 print (true); // V.1
print (33.5); // erreur car ambigu¨e }
c 2006 Marc Feeley IFT2030 page 529
Sélection de procédure surdéfinie en C++ (1)
•
Cas à 1 paramètre: sélection par spécificité1. Recherche d’une procédure avec paramètre formel de même type que paramètre actuel
2. Nouvelle recherche après promotion numérique du paramètre actuel
• enum,bool,char,short,int -> int
• long -> long
• float,double -> double
3. Nouvelle recherche en considérant les conversions standard du paramètre actuel:
• élargissement, p.e. char -> long
• rétrécissement, p.e. float -> int
• T* -> void*
c 2006 Marc Feeley IFT2030 page 530
Cas à plus d’un paramètre
•
Éliminer les procédures qui sont moins spécifiques qu’une des autres sur au moins un des paramètres (mˆeme > prom > conv)•
L’appel est ambiguë s’il ne reste pas exactement une procédure•
Exemple avec 2 paramètresvoid f (char x, int y) { ... } // V.1 void f (int x, char y) { ... } // V.2
void test () // V.1 V.2
{f (’A’,123); // V.1 mˆeme+mˆeme prom+conv f (123,’A’); // V.2 conv+prom mˆeme+mˆeme f (8.7,true);// V.1 conv+prom conv+conv f (123,123); // ambigu¨e conv+mˆeme mˆeme+conv }
c 2006 Marc Feeley IFT2030 page 531
Surdéfinition des opérateurs (1)
•
C++ permet de surdéfinir +, -, <<, etc•
Utile pour mieux intégrer les types usagers au langage (nombres, impression)•
Se fait comme une définition de fonction avec le nom“operator<op ´erateur>” et au moins un paramètre doit être un struct ou class
c 2006 Marc Feeley IFT2030 page 532
Surdéfinition des opérateurs (2)
•
Exemple: implantation des nombres complexestypedef struct cpx { double reel, imag; } cpx;
cpx operator+ (cpx a, cpx b) { cpx r;
r.reel = a.reel + b.reel;
r.imag = a.imag + b.imag;
return r;
}
cpx operator+ (cpx a, double b) { cpx t;
t.reel = b;
t.imag = 0;
return a + t;
}
void test () { cpx x, y, z;
x = (y + (1 + 2)) + z;
}
c 2006 Marc Feeley IFT2030 page 533
Activation de procédure (1)
•
Objectif: expliquer les mécanismes d’activation de procédure et d’accès aux variables•
Problème fondamental: comment associer une valeur à une variable?Nom de variable --> valeur de la variable
•
Décomposé en 3 étapes1. Nom de var. --> déclaration de var.
• règles de portée
2. Déclaration de var. --> emplacement mém.
• activation courante
3. Emplacement mém. --> valeur
• état de la mémoire
c 2006 Marc Feeley IFT2030 page 534
Activation de procédure (2)
•
La deuxième étape est nécessaire pour traiter les procédures récursives•
Déf: une procédure est récursive s’il est possible que pendant une de ses activation elle soit activée ànouveau
• Directement récursive: f-->f
• Indirectement récursive: f-->g-->f
c 2006 Marc Feeley IFT2030 page 535
Activation de procédure (3)
•
Exemple en Pascalprogram recursif;
var x : integer;
function f(n:integer) : integer;
begin
if n <= 1 then f := 1
else
f := n * f(n-1);
end;
begin x := f(3); writeln(x); end.
c 2006 Marc Feeley IFT2030 page 536
Activation de procédure (4)
•
Il y a 3 activations de f et chaque activation a sa propre instance de la variable nprogram recursif;
+---+
var x : integer; x | ? | +---+
+---+ +---+ +---+
n | 3 | n | 2 | n | 1 |
+---+ +---+ +---+
function f(n:integer) function f(n:integer) function f(n:integer)
: integer; : integer; : integer;
begin begin begin
if n <= 1 then if n <= 1 then if n <= 1 then
f := 1 f := 1 f := 1
else else else
f := n * f(n-1); f := n * f(n-1); f := n * f(n-1);
end; end; end;
begin x := f(3); writeln(x); end.
•
La récursivité implique l’allocation dynamique desvariables locales et paramètres formels (en général le nombre d’appels récursifs n’est pas connu
statiquement)
c 2006 Marc Feeley IFT2030 page 537
Activation: cas non-récursif (1)
•
L’espace pour stocker les paramètres et variableslocales des procédures non-récursives peut être alloué statiquement (p.e. FORTRAN)
c 2006 Marc Feeley IFT2030 page 538
Activation: cas non-récursif (2)
•
Exemple, compilation de Pascal à C (qui est utilisé comme un “langage machine” de haut niveau)Programme Pascal C sans param`etres/r´esultat program non_recursif; |int x;
|
var x : integer; |int mult_n, mult_m, mult_res;
|
function mult(n,m:integer)|void mult()
: integer; |{
begin | mult_res = mult_n * mult_m;
mult := n*m; |}
end; |
|void non_recursif()
begin |{
x := mult(2,3); | mult_n = 2;
writeln(x); | mult_m = 3;
x := mult(4,5); | mult();
writeln(x); | x = mult_res;
end. | printf("%d",x);
|
| mult_n = 4;
| mult_m = 5;
| mult();
| x = mult_res;
| printf("%d",x);
|}
c 2006 Marc Feeley IFT2030 page 539
Activation: cas non-récursif (3)
•
Pour expliquer plus en détail les transferts de contrôle requis pour une activation, nous utilisons les “gotocalculés” qui existent sur certains compilateurs C (gcc)
• &&<´etiqu> => pointeur (de type void*)
• goto *<ptr ´etiqu>; => saut à <´etiqu>
• Exemple
void *dest;
if (x<0) dest = &&neg; else dest = &&pos;
goto *dest;
neg: ...
pos: ...
c 2006 Marc Feeley IFT2030 page 540
Activation: cas non-récursif (4)
•
La procédure appelante doit indiquer à la procédure appelée où il faut retourner le contrôle aprèsl’activation (adresse de retour)
•
L’adresse de retour est simplement un paramètre implicite de toutes les procéduresc 2006 Marc Feeley IFT2030 page 541
Activation: cas non-récursif (5)
Programme Pascal C sans proc´edures program non_recursif; |int x;
|
var x : integer; |void *mult_ret;
|int mult_n, mult_m, mult_res;
function mult(n,m:integer)|
: integer; |void non_recursif()
begin |{
mult := n*m; | mult_n = 2;
end; | mult_m = 3;
| mult_ret = &&point1;
begin | goto mult;
x := mult(2,3); |point1:
writeln(x); | x = mult_res;
x := mult(4,5); | printf("%d",x);
writeln(x); |
end. | mult_n = 4;
| mult_m = 5;
| mult_ret = &&point2;
| goto mult;
|point2:
| x = mult_res;
| printf("%d",x);
|
| return;
|
|mult:
| mult_res = mult_n * mult_m;
| goto *mult_ret;
|}
c 2006 Marc Feeley IFT2030 page 542
Activation: cas récursif (1)
•
Ceci ne fonctionne pas avec les procédures récursivesprogram recursif; |int x;
|
var x : integer; |void *f_ret;
|int f_n, f_res;
function f(n:integer)|
: integer; |void recursif()
begin |{ f_n = 3; f_ret = &&point1;
if n <= 1 then | goto f;
f := 1 |point1:
else | x = f_res;
f := n * f(n-1); | printf("%d",x);
end; | return;
|
begin |f: if (f_n <= 1)
x := f(3); | f_res = 1;
writeln(x); | else
end. | { f_n = f_n-1; f_ret = &&point2;
| goto f;
| point2:
| f_res = f_n * f_res;
| }
| goto *f_ret;
|}
•
La valeur de f n et de f ret au retour de f sont incorrectesc 2006 Marc Feeley IFT2030 page 543
Activation: cas récursif (2)
•
L’état d’une activation est conservé dans un bloc d’activation qui stocke• Les paramètres formels (incluant l’adresse de retour)
• Les variables locales et temporaires (introduites par le compilateur)
• Le résultat de la fonction (pas vraiment nécessaire car allocation statique possible)
• Le lien dynamique qui chaîne les blocs des activations qui n’ont pas encore terminées
•
Le lien dynamique permet de retrouver le bloc de la procédure appelante au moment du retour de laprocédure courante
c 2006 Marc Feeley IFT2030 page 544
Activation: cas récursif (3)
program recursif; |typedef struct bloc
| { int n; void *ret; struct bloc *dyn; } bloc;
var x : integer; |
|bloc *bac, *p;
function f(n:integer)|int x;
: integer; |int f_res;
begin |
if n <= 1 then |void recursif()
f := 1 |{
else | bac = NULL;
f := n * f(n-1); |
end; | p = malloc(sizeof(bloc));
| p->n = 3; p->ret = &&point1;
begin | p->dyn = bac; bac = p;
x := f(3); | goto f;
writeln(x); |point1:
end. | p = bac->dyn; free(bac); bac = p;
| x = f_res;
| printf("%d",x);
| return;
|
|f: if (bac->n <= 1)
| f_res = 1;
| else
| { p = malloc(sizeof(bloc));
| p->n = bac->n-1; p->ret = &&point2;
| p->dyn = bac; bac = p;
| goto f;
| point2:
| p = bac->dyn; free(bac); bac = p;
| f_res = bac->n * f_res;
| }
bac = NULL | goto *bac->ret;
|}
c 2006 Marc Feeley IFT2030 page 545
Activation: cas récursif (4)
program recursif; |typedef struct bloc
| { int n; void *ret; struct bloc *dyn; } bloc;
var x : integer; |
|bloc *bac, *p;
function f(n:integer)|int x;
: integer; |int f_res;
begin |
if n <= 1 then |void recursif()
f := 1 |{
else | bac = NULL;
f := n * f(n-1); |
end; | p = malloc(sizeof(bloc));
| p->n = 3; p->ret = &&point1;
begin | p->dyn = bac; bac = p;
x := f(3); | goto f;
writeln(x); |point1:
end. | p = bac->dyn; free(bac); bac = p;
| x = f_res;
| printf("%d",x);
| return;
|
|f: if (bac->n <= 1)
| f_res = 1;
| else
| { p = malloc(sizeof(bloc));
| p->n = bac->n-1; p->ret = &&point2;
| p->dyn = bac; bac = p;
| goto f;
| point2:
+---+ | p = bac->dyn; free(bac); bac = p;
bac-->| 3 | n | f_res = bac->n * f_res;
|&&point1| ret | }
| NULL | dyn | goto *bac->ret;
+---+ |}
c 2006 Marc Feeley IFT2030 page 546
Activation: cas récursif (5)
program recursif; |typedef struct bloc
| { int n; void *ret; struct bloc *dyn; } bloc;
var x : integer; |
|bloc *bac, *p;
function f(n:integer)|int x;
: integer; |int f_res;
begin |
if n <= 1 then |void recursif()
f := 1 |{
else | bac = NULL;
f := n * f(n-1); |
end; | p = malloc(sizeof(bloc));
| p->n = 3; p->ret = &&point1;
begin | p->dyn = bac; bac = p;
x := f(3); | goto f;
writeln(x); |point1:
end. | p = bac->dyn; free(bac); bac = p;
| x = f_res;
| printf("%d",x);
| return;
|
|f: if (bac->n <= 1)
| f_res = 1;
+---+ | else
bac-->| 2 | n | { p = malloc(sizeof(bloc));
|&&point2| ret | p->n = bac->n-1; p->ret = &&point2;
| . | dyn | p->dyn = bac; bac = p;
+---|----+ | goto f;
V | point2:
+---+ | p = bac->dyn; free(bac); bac = p;
| 3 | n | f_res = bac->n * f_res;
|&&point1| ret | }
| NULL | dyn | goto *bac->ret;
+---+ |}
c 2006 Marc Feeley IFT2030 page 547
Activation: cas récursif (6)
program recursif; |typedef struct bloc
| { int n; void *ret; struct bloc *dyn; } bloc;
var x : integer; |
|bloc *bac, *p;
function f(n:integer)|int x;
: integer; |int f_res;
begin |
if n <= 1 then |void recursif()
f := 1 |{
else | bac = NULL;
f := n * f(n-1); |
end; | p = malloc(sizeof(bloc));
| p->n = 3; p->ret = &&point1;
begin | p->dyn = bac; bac = p;
x := f(3); | goto f;
writeln(x); |point1:
end. | p = bac->dyn; free(bac); bac = p;
+---+ | x = f_res;
bac-->| 1 | n | printf("%d",x);
|&&point2| ret | return;
| . | dyn |
+---|----+ |f: if (bac->n <= 1)
V | f_res = 1;
+---+ | else
| 2 | n | { p = malloc(sizeof(bloc));
|&&point2| ret | p->n = bac->n-1; p->ret = &&point2;
| . | dyn | p->dyn = bac; bac = p;
+---|----+ | goto f;
V | point2:
+---+ | p = bac->dyn; free(bac); bac = p;
| 3 | n | f_res = bac->n * f_res;
|&&point1| ret | }
| NULL | dyn | goto *bac->ret;
+---+ |}
c 2006 Marc Feeley IFT2030 page 548
Activation: gestion mémoire (1)
•
Quand faut-il récupérer l’espace occupé par le bloc d’activation?•
La plupart des langages le font au retour del’activation (exceptions: Modula-3, Oberon, Lisp, Scheme)
•
Un problème relié à ce choix, en C, c’est quel’opérateur “&” peut engendrer des pointeurs fous
int *p;
void print () { printf ("%d\n", *p); } void proc (int n) { p = &n; print (); } void main () { proc (123); print (); }
c 2006 Marc Feeley IFT2030 page 549
Activation: gestion mémoire (2)
•
Dans les langages comme C et Pascal, qui spécifient que le bloc d’activation est récupéré au retour del’activation, il est possible d’allouer les blocs d’activation sur une pile car le bloc d’activation de l’appelée est
seulement actif pendant l’activation de l’appelant
•
Dans ce cas, le lien dynamique n’a plus besoin d’être stocké explicitement car les blocs sont contigusc 2006 Marc Feeley IFT2030 page 550
Activation: gestion mémoire (3)
program recursif; | typedef struct bloc
| { int n;
var x : integer; | void *ret;
| } bloc;
function f(n:integer) |
: integer; | bloc pile[100], *bac;
begin | int x;
if n <= 1 then | int f_res;
f := 1 |
else | void recursif()
f := n * f(n-1); | { bac = pile;
end; |
| (bac+1)->n = 3;
begin | (bac+1)->ret = &&point1;
x := f(3); | bac++;
writeln(x); | goto f;
end. | point1:
| bac--;
| x = f_res;
+---+ | printf("%d",x);
bac-->| 1 | n | return;
|&&point2| ret |
+---+ | f: if (bac->n <= 1)
| 2 | n | f_res = 1;
|&&point2| ret | else
+---+ | { (bac+1)->n = bac->n-1;
| 3 | n | (bac+1)->ret = &&point2;
|&&point1| ret | bac++; goto f;
+---+ | point2:
| | n | bac--;
| | ret | f_res = bac->n * f_res;
+---+ | }
| goto *bac->ret;
| }
c 2006 Marc Feeley IFT2030 page 551
Activation: gestion mémoire (4)
•
L’allocation et la récupération des blocs d’activation peuvent se faire d’un seul coup (tel que faitprécédemment) ou incrémentallement
•
L’allocation et récupération incrémentale se fait en partie dans l’appelant et en partie dans l’appeléc 2006 Marc Feeley IFT2030 page 552
Activation: gestion mémoire (5)
•
Par exemple, les compilateurs C font normalement• L’allocation d’un bloc contenant les paramètres dans l’appelant (empilés dans l’ordre droite à gauche)
• L’extension de ce bloc avec les variables locales dans l’appelé
• La contraction de ce bloc en quittant la portée des variables locales dans l’appelé
• La récupération du bloc au retour de l’activation dans l’appelant
c 2006 Marc Feeley IFT2030 page 553
Activation: gestion mémoire (6)
•
Exemple en Cvoid proc (int x, int y) { int a;
a = x-y;
if (a < 0)
{ int b; b = a*a; printf ("%d", b); } printf ("%d %d %d", x, y, a); /* 1 2 -1 */
}
void test () { proc (1, 2); }
y x
y x a
y x a b
y x a b
y x a b
y x a
y x a
y x a
y x
ret
ret ret ret ret ret ret ret ret ret ret ret
ret ret ret ret ret ret ret ret
fmt
ret
ret
2 1
2 1 -1
2 1 -1
1
2 1 -1
1 1
"%d"
2 1 -1
1
2 1 -1
2 1 -1
2 1 -1
2 1
fmt "%d %d %d"
-1 2 1
test proc proc proc printf proc proc printf proc test
c 2006 Marc Feeley IFT2030 page 554
Activation: gestion mémoire (7)
•
Empiler les paramètres de droite à gauche permet une implantation simple des fonctions à arité variablecomme printf:
int printf (char* fmt, ...) /* v´eritable "..." */
{
/* utiliser "fmt" pour obtenir les autres param`etres */
}
c 2006 Marc Feeley IFT2030 page 555
Activation: procédures imbriquées (1)
•
Une approche répandue pour gérer la mémoire est de la subdiviser en 3 zones1. zone statique: variables globales et code 2. pile: paramètres et variables locales
3. tas: autres allocations dynamiques
00000 00000 00000 00000 11111 11111 11111 11111 0000000
0000000 0000000 0000000 1111111 1111111 1111111 1111111
00000 00000 00000 00000 11111 11111 11111 11111
zone statique tas pile
•
L’accès à une variable globale est simple car l’adresse de la variable dans la zone statique est connue à lacompilation
c 2006 Marc Feeley IFT2030 page 556
Activation: procédures imbriquées (2)
•
L’accès à une variable non-globale demande detrouver le bloc d’activation la contenant et d’accéder au champ approprié de ce bloc
• En C et FORTRAN ceci est relativement simple
puisque la variable est nécessairement dans le bloc d’activation courant
• Avec des procédures imbriquées ceci n’est plus vrai
c 2006 Marc Feeley IFT2030 page 557
Activation: procédures imbriquées (3)
•
Exemple en Pascal avec lien dynamiqueprogram imbrique;
var x : integer;
procedure aa (a : integer);
procedure bb (b : integer);
begin writeln (b + a); end;
procedure cc (c : integer);
begin bb (c*10); end;
begin bb (10); cc (20); end;
begin aa (1); (* 11 201 *) aa (2); (* 12 202 *) end.
dyn ret
a 1
NULL
aa_1
dyn ret
b 10
dyn ret
a 1
NULL
bb_1
aa_1
dyn ret
a 1
NULL
aa_1
dyn ret
c 20
dyn ret
a 1
NULL
aa_1 cc_1
dyn ret
b 200
dyn ret
a 1
NULL
dyn ret
c 20
cc_1 bb_2
aa_1
c 2006 Marc Feeley IFT2030 page 558
Activation: procédures imbriquées (4)
•
Déf: le parent dynamique d’une activation X c’est l’activation Y qui a créée l’activation Ximbrique_1 aa_1
cc_1 bb_1
bb_2
aa_2 bb_3 cc_2 bb_4
•
Déf: le parent lexical d’une activation X c’estl’activation Y qui a déclarée la procédure dont l’appel a créé l’activation X
imbrique_1 aa_1
cc_1 bb_1
aa_2 bb_3 cc_2
bb_2 bb_4
•
Le lien dynamique chaîne les parents dynamiques et le lien statique chaîne les parents lexicauxc 2006 Marc Feeley IFT2030 page 559
Activation: procédures imbriquées (5)
•
La chaîne statique permet de trouver le bloc d’activation contenant une variable à accéder•
Déf: le niveau d’imbrication d’une procédure c’est sa profondeur dans l’arbre d’imbrication (avec racine = 1)bb aa imbrique
cc
niveau 1 niveau 2 niveau 3
c 2006 Marc Feeley IFT2030 page 560
Activation: procédures imbriquées (6)
•
Si l’exécution se trouve dans la procédure X de niveau xet qu’il faut accéder à une variable V déclarée dans la procédure englobante Y de niveau y alors
• le bloc contenant V est à une distance de x − y dans la chaîne statique par rapport au bloc d’activation
courant
imbrique_1 aa_1
cc_1 bb_1
aa_2 bb_3 cc_2
bb_2 bb_4