Témoignage
 

Rubrique « Info »

 



É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").


               
 
Avertissement !
 
Décollage !  |  Présentation du site web "AILES"  | 
Infos générales  |  articles "Informatique"