TD5 : Modes d’appel, Portée

1. Portée

1)

a).

int find_max (int a[], int i, int j)
{
int maxl, maxr, k;
if (i==j)
    return a[i];
k = (i+j)/2;
maxl = find_max (a, i, k);
maxr = find_max (a, k, j);
return (maxl > maxr)?maxl:maxr;
}

avec des static :

int find_max (int a[], int i, int j)
{
static int maxl, maxr, k;
if (i==j)
    return a[i];
k = (i+j)/2;
maxl = find_max (a, i, k);
maxr = find_max (a, k, j);
return (maxl > maxr)?maxl:maxr;
}

Les variables sont allouées une fois pour toutes : variables = “globales dans la fonction”.

Seule find_max a accès à ces variables, mais une fois qu’elles ont été allouées (au premier appel), elles sont stockées au même endroit.

**Opposé des variables statiques ** : les variables auto

⟹ Le programme bogue.

b).

let maxl = ref 0 in
let maxr = ref 0 in
    let rec find_max a i j =
    if i=j
        then a.(i)
    else let k = (i+j)/2 in begin
        maxl := find_max a i k;
        maxr := find_max a k j;
        if !maxl > !maxr then !maxl else !maxr
    end;;

C’est comme si les variables maxl, maxr étaient statiques, au sens précédent.

2).

a).

let x=3 in
let y=x+1 in
let x=12 in
x+y

16 : CamL = langage à portée lexicale

b).


let x=3 in
let f y = x+y in
let x=4 in
f 5

8

c).


let f = (let x=3 in fun y -> x+y) in
f 4

7

d).


let f = (let x=3 in
let y=x+1 in
fun x -> x+y) in
let x=5 in
f x

9

3).



type 'a variable = 'a ref;;
let rec mkvar v = ref v;;
let rec fluid_let (x : 'a variable) (e : 'a) (body : unit -> 'b) =
let save_x = !x in
let result = (x := e; body ()) in
begin
x := save_x;
result
end;;

Attention : fluid_let avec des ref : un problème qui a mis 20 ans à être résolu.


let i = 7 in
let f = fun x -> x+i in
let i = 0 in
f 3
let i = mkvar 0;;
fluid_let i 7 (fun () ->
fluid_let f (fun x -> x + !i) (fun () ->
fluid_let i 0 (fun () ->
!f 3)))

retourne 3

c).

(setq i 7)
(defun f (x) (+ x i))
(let ((i 0))
(f 3))

retourne : 3

let i = 7;;
let rec f x = x+i;;
let i = 0 in
f 3;;

retourne : 10

d).

let i = mkvar 0;;
try
fluid_let i 12 (fun () -> raise Failure "arg")
with Failure _ -> !i

12, car on est sorti du fluid_let (à cause de l’exception) avant d’avoir pu rétablir la valeur de i.

⟶ Dans fluid_let : on aurait dû gérer le cas des exceptions.

e).

Threads :

bouts de programme qui s’exécutent en parallèle à l’intérieur d’un programme.

Processus en parallèle : travaillent dans des espaces mémoires séparés

VS

threads : à l’intérieur d’un programme, partagent des données communes.

⟹ On peut gérer des exceptions, mais les threads : c’est un mic-mac impossible à gérer, si deux threads font des fluid_let sur la même variable : les valeurs de cette variable change selon que l’un soit plus rapide que l’autre, etc…

Exs :

  • Serveur Apache : crée des threads ⟶ clients traités en parallèle
  • Node.js : paradigme de programmation non bloquante.

4).

(setq i 1)
(defun g (y) (+ x y))
(defun f (x) (+ (g x) i))

retourne 67

NB : Remarquons qu’en C : stdin, stdout sont à liaisn dynamique : pratique dans ce contexte.

5).

$𝛼$-renommage : plus possible facilement en Lisp :

(setq i 1)
(defun g (y) (+ x y))
(defun f (z) (+ (g z) i))

ne retourne plus du tout la même chose !

6).

Scheme : C’est un Lisp à portée statique = portée lexicale

ComonLisp : est statique par défaut, mais toute variable peut être rendue dynamique avec une fonction.

II.

7.

ne fait rien : les variables échangées sont locales.

8.

  • En C : avec des pointeurs
  • En C++, Pascal : appel par référence
  • Java :
    • avec des accesseurs
    • OU : avec des objets

9.

Fortran : en appel par référence.

SUBROUTINE SWAP (I, J)
INTEGER K
K=I
I=J
J=K
END

NB: GOD is REAL unless declared INTEGER : les variables commençant par G, en Fortran, sont des réels.

a) CALL SWAP(I,J) : échange I et J (appel par référence)

b) CALL SWAP(I+0,J) :

- selon la norme : met la valeur de `J` dans `I`  (appel par référence)
- mais en vrai : échange `I` et `J`

c) CALL SWAP(I+0,J*1)

10.

int abs (int i) {
if (i < 0)
return -i;
else return i;
}

#define ABS(i) ((i)<0)?(-(i)):(i)

a) vu en cours b) Par valeur le i++ est calculé qu’une fois

VS : Paresseuse : i++ est fait deux fois c)

fputc VS putc :

#define putc(c,f) do { \
int __i = (f)->buflen; \
if (__i->buflen>=MAXBUFLEN) { fflush(f); __i=0; } \
(f)->buf[__i++] = c; \
(f)->buflen = __i; \
} while (0)

void fputc (int c, FILE *f) {
putc (c, f);
}
  • putc :
    • évalue plusieurs fois ses argument, à cause de l’appel paresseux
    • mais plus rapide
  • fputc :
    • appel par valeur : argument évalué qu’une fois
    • plus lent

inline : mot clé qui permet de recopier le code tel quel plutôt que de rappeler une fonction :

  • permet optimisation, car le compilateur “apprend” le comportement de la fonction appelée

c).

#define : la définition = sur toute la ligne : pour écrire plusieurs lignes : \

\ ⟶ C bouffe le caractère suivant (si c’est un retour chariot, bouffe le passage à la ligne)

__i : pour être quasi sûr que l’utilisateur n’ait pas utilisé ce nom de variable (même si c’est dans un scope local : attention à c, pourrait être un f(i))

while (e){

 // code

}

do {

    // code

}  while(e)

Pourquoi

do {

    // code

}  while(0)

?

  1. Protège le scope
  2. En fait :
if(e)
    putc(c,f);
else
    //

Si putc(c,f) = { // code }; seulement :

alors appel par nom :

if(e)
    { // code };;
else
    //

: le else est ignoré !

11.

real procedure Sum(k, l, u, ak)
value l, u;
integer k, l, u;
real ak;
begin
real s;
s := 0;
for k := l step 1 until u do
s := s + ak;
Sum := s
end;

⟶ somme des valeurs $V[i]$


int hd(stream *s){
    return s -> head;
}


stream *tl(stream *s){
    return force(s-> next);
}

thunk *cons(int i, thunk *next){


}
hamming :: [Integer]

hamming = 1 : mergeUnique (map (2*) hamming)
                        (mergeUnique (map (3*) hamming)
                                    (map (5*) hamming))

Ensemble des entiers qui s’écrivent $2^a 3^b 5^c$ triée !

Leave a comment