|

Écrivez
à AILES ! |

Retour
vers programmation |

Retour
vers les questions Langage |

Retour
vers les questions Binding |
|
|
Questionnaire Langage de programmation
Binding
[umd], [umtcs], [NCST],
[javaworld]
|
Le mécanisme de liaison est sans
doute le plus ancien de l'histoire de la programmation.
Présent à tous les stades du logiciel (de son écriture à
son exécution), le binding va avoir une influence profonde
sur la nature même du langage utilisé.
Et lorsque l'on veut lier une entité à un
type,... on tombe dans le typage, abstraction fondamentale à
la base de toute programmation (cf. les types) |
Binding :
Ce mécanisme de la réponse aux
questions "quoi
?", "comment ?"
et "quand ?" :
But du binding :
"Quoi relier ?"
La liaison concerne des entités de programme, dont on peut
distinguer ([NCST])
:
- Name binding (liaison de nom) :
assignation d'un nom (identifiant) à une variable);
- Address binding (liaison d'adresse)
: assignation d'une adresse à, par exemple, une variable;
- Value binding (liaison de valeur)
: assignation d'une valeur à, par exemple, une variable;
- Type binding (assignation de type)
: assignation d'un type à, par exemple, une variable.
" Comment choisir l'interprétation des
morceaux de mémoires ? ". Ces morceaux ont une
sémantique (relié à une entité de
programme, comme une structure de donnée, une variable, une fonction,
un pointeur...) associées depuis le programme jusqu'à sa
représentation, en exécutable. Le binding permet de retrouver cette sémantique
derrière les n bits du programme exécutable.
Le binding fait donc fondamentalement référence
à une association, et cette
association intervient aussi bien lors de :
- la conception du langage (exemple : int
est associé au type "integer" dès la conception du langage
Java) ;
- l'implémentation du langage (exemple :
en C, nbIteration = 5 est implémenté lors de la compilation en un
entier, de part l'implémentation de ce langage. Et encore, selon les
premiers compilateurs, cet entier pouvait être sur 16, 32 ou 64
bits!)
- la compilation du langage (exemple : int
nbIteration associe le type int à la variable nbIteration lors de la
compilation) ;
- le chargement du langage (les variables
globales en C/C++);
- l'exécution du langage (en Python,
"nbIteration = 5" lie le type entier à la variable
nbIteration lors de l'exécution).
" Quand cette association entre l'exécutable et l'entité du
programme intervient ? "
- statiquement : cette liaison
intervient avant l'exécution (lors de la compilation et du link) et
reste inchangée pendant toute l'exécution du programme.
Cela est le cas
de FORTRAN (où toute les données mémoire pour les variables locales
sont allouées une seule fois par le compilateur, et mémorisée
pendant la vie du programme) ;
- dynamiquement : la liaison se crée ou
se modifie pendant l'exécution du programme.
Cela se retrouve en C/C++ et Java, où les données mémoire pour les variables
locales sont allouées pendant l'exécution, à chaque invocation de
"sous-programme" (fonction), et désallouées lors de la
sortie de ces mêmes fonctions.
Cela implique d'ailleurs que les langages C/C++/Java peuvent
bénéficier de méthodes récursives, au contraire de FORTRAN.
Le binding statique peut être :
- plus sûr (vérification des types lors de la compilation) ;
- plus efficace (allocation unique lors de la compilation de plusieurs
ensembles de données pour plusieurs exécution).
Le binding dynamique est :
- plus flexible, basé sur les valeurs réelles des entités du
programme (ce qui donne les fonctions virtuelles C++);
- plus évolutif dynamiquement, avec l'ajout de code pendant
l'exécution (comme en Java, Smalltalk, Lisp et ML).
Ce mécanisme de liaison a les conséquences
suivantes :
Dès que l'on relie un nom à plusieurs
variables, on est amené à déterminer à quel variable correspond le
nom utilisé dans une ligne de programmation donnée. D'où la
question :
" Comment déterminer l'étendue de programme où une variable
est visible ? " (Scoping)
La liaison de nom (Name binding) fonctionne différemment selon le
type de scoping utilisé :
Static scoping : cette étendue de programme est basée sur la
structure lexicale (la syntaxe) du programme, et peut donc être
déterminée par le compilateur.
La déclaration de la variable est alors retrouvée depuis le bloc
initial (où l'on fait usage de cette variable) jusqu'aux blocs englobant
(nested scoping). Le bloc est déterminé par exemple par une accolade
en C et C++. Le premier bloc a contenir la déclaration de la variable
utilisée fait foi.
Dynamic scoping : basée sur la pile d'appel (calling
sequence)
lords de l'exécution du programme.
Plus souple, le dynamic scoping est aussi moins "lisible"
car :
- on ne peut dire en lisant le code à quel type une variable fait
référence ;
- ce type peut changer à chaque utilisation, et ce en fonction de la
pile d'appel actuelle, c.à.d. "l'environnement
d'exécution".
Vouloir relier une adresse à une
variable implique de savoir quel mécanisme utiliser, d'où la
question :
" Comment mémoriser (allouer / désallouer) une entité de
programme ? "
(en
anglais: "storage binding" ou "extent")
=> Allocation : quand allouer de la mémoire pour une entité de
programme (cf. plus haut première question)
=> Désallocation : quand désallouer la mémoire alors que
l'entité de programme (comme une variable) n'est plus utilisée ?
Allocation statique : la mémoire est allouée avant l'exécution du
programme (le plus souvent lors de la phase de compilation) et se
désalloue ors de la terminaison du programme (c'est le cas des
variables globales C++).
Cela a pour avantage :
- un adressage direct (puisque l'allocation de mémoire est connue
dès la compilation);
- une efficacité accrue pendant l'exécution (puisqu'il n'y a pas
d'opération dynamique pour allouer / désallouer de la mémoire).
Allocation dynamique : la mémoire est allouée / désallouée pendant
l'exécution du programme :
- soit lors de l'entrée / sortie d'un bloc.
- soit sur demande (new / delete);
Cela a pour avantage :
- de permettre les procédures récursives et la gestion explicite de
la mémoire par le programmeur;
- de permettre la construction de structure dynamique de structure de
données (comme les listes chaînées), à partir de pointeur ou
référence.
Cela a bien sûr des inconvénients :
- de nombreuses sources de bugs qu'entraîne une gestion manuelle de
la mémoire (fuite de mémoire) ;
- un coût d'exécution lié aux procédures récursive (en terme de
performance et surtout de mémoire avec une pile d'appel qui grossit).
On notera que cet inconvénient là se fait de plus en plus discret,
avec des mémoires vives dont la taille avoisine souvent le demi Go...
même pour de simple station de travaille individuelle (en langage
clair, le PC de bureau vient avec 256, voire 512Mo) ;
" Comment lier une entité de
programmation à un type ? "
Donc comment savoir que cette variable ou ce paramètre est de tel ou
tel type ?
Trois moyens :
Typage statique (static type binding) :
Ce typage peut être un :
* typage statique déclaratif
explicite : C, C++, Java, Pascal (typage "fort",
qui correspond bien à la mentalité très "explicite" des informaticiens)
* typage statique déclaratif
implicite : Perl
(tout nom commençant par @ est un tableau), Basic. Cela entraîne des conversions implicite de type que le
compilateur ne peut détecter... surtout lorsque une fonction est
compilée séparément du programme principal : le compilateur ne sera
pas conscient du typage incompatible !
Fortran (comme cela est
rappelé dans l'article sur l'héritage de type),
mélange :
- déclaration explicite ;
- déclaration implicite (si le nom d'une variable commence par
I,J,K,L,M ou N, cette variable est implicitement déclarée en tant
que INTEGER, sinon elle est implicitement déclarée en tant que
REAL).
On notera la distinction entre :
- déclaration (int i;)
- définition (i = 1;)
Typage dynamique
(dynamic type binding) :
Le type d'une entité de programmation n'est déterminé que par le
membre droit d'une assignation.
Cela permet une grande flexibilité et introduit tout un paradigme
indépendant de la programmation objet : la programmation générique.
Il existe des fonctions générique en Perl ou en AWK, mais c'est le
langage Eiffel qui a poussé la programmation générique le plus
loin, avec la généricité contrainte
qui permet de combattre des désavantages de cette sorte de binding,
à savoir :
- l'impossibilité pour le compilateur de détecter des types
incompatibles ;
- le surcoût induit par la vérification dynamique de type (d'où :
* une exécution plus lente ;
* la nécessité d'avoir un descripteur de type
associé à tout objet en mémoire afin de permettre cette
vérification dynamique de type ;
* Le stockage d'une variable ne peut être prévu
que dynamiquement puisque l'espace nécessité par son type n'est pas
connu statiquement.
Les langages qui utilisent le typage dynamique sont le Python, APL,
SNOBOL4 et la plupart des versions de Lisp.
L'équivalent hardware du typage dynamique passe par une machine à
architecture tagguée, comme cela est rappelé dans l'article sur les
types.
Typage inféré (ou "déduit")
(type inference binding) :
Dans langages fonctionnels comme ML, Miranda ou Haskell emploient un
mécanisme d'inférence (déduction) de type.
Ainsi en ML, les déclarations suivantes de fonctions seraient :
fun circumf(r) = 3.14159 * r * r; : correcte (spécifie une fonction
prenant un réel en argument et rendant un réel)
fun square(x) = x * x; : incorrecte (impossible de déduire le type de
donnée manipulée par l'opérateur 'x')
fun square(x) = int = x * x; : correcte (le programmeur laisse un
indice indiquant le type de 'x')
Rq : amusant, [umtcs]
choisit la fonction 'square' comme exemple "qui ne marche
pas"... alors que, en anglais, "to be square"
signifie en gros (et en argot!) "l'avoir dans l'os" (même si le sens
académique signifie plutôt "être mal approprié", ou
"mal assorti").
|