Nous allons à présent terminer notre tour d'horizon des identificateurs avec deux notions assez complexes: l'
édition des liens et les
liaisons.
Avant toute chose vous devez savoir que ces notions ne concerne que les identificateurs de fonction et d'objet.
Alors, de quoi s'agit-il?
Citation : C11 (n1570) § 6.2.2 al 1 p 36
An identifier declared in different scopes or in the same scope more than once can be
made to refer to the same object or function by a process called linkage.
L'édition des liens est la dernière phase de la compilation. Il s'agit d'un procédé permettant à plusieurs identificateurs de faire référence au même objet ou à la même fonction. Prenons un exemple simple:
test.c
Secret (cliquez pour afficher)
Code : C | #include <stdio.h>
void
affiche(void)
{
puts("salut");
}
|
main.c
Secret (cliquez pour afficher)
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13 | #include <stdio.h>
#include <stdlib.h>
void affiche(void);
int
main(void)
{
affiche();
return EXIT_SUCCESS;
}
|
Comme vous le voyez, nous avons deux fichiers:
- un fichier test.c définissant un identificateur de fonction "affiche";
- un fichier main.c déclarant un identificateur de fonction "affiche".
A la lumière de ce que vous avez vu auparavant quelque chose devrait vous choquer. En effet, l'identificateur du fichier main.c est déclaré et non défini (le corps de la fonction n'est pas spécifié), dès lors cet identificateur ne fait référence à rien du tout... Pourtant, la compilation ne pose aucun problème, comme si l'identificateur déclaré dans le fichier main.c faisait référence à la fonction définie dans le fichier test.c. En réalité, c'est exactement ce qu'il se passe
Mais?! Comment le compilateur fait-il pour savoir que ces deux identificateurs référence la même fonction?
Tout d'abord, sachez que ce n'est pas le compilateur qui se charge de cette tâche, mais l'
éditeur de liens.
Ensuite, c'est ici qu'intervient la notion de
liaison. Chaque identificateur dispose d'une liaison qui peut être de trois types: externe, interne ou aucune.
Citation : C11 (n1570) § 6.2.2 al 1 p 36
There are three kinds of linkage: external, internal, and none.
Et comment fait-on pour savoir de quel type de liaison dispose un identificateur?
Et bien, la liaison d'un identificateur dépend de la position de sa déclaration, de son type et de l'utilisation du spécificateur
static ou
extern. Heureusement pour
nous vous, les règles sont assez simples. Attention suivez bien!
- toute déclaration d'un identificateur de fonction a une liaison externe sauf si cette dernière est précédée du spécificateur
static, auquel cas elle a une liaison interne;
- toute déclaration d'un identificateur d'objet
en dehors de tout bloc a une liaison externe sauf si cette dernière est précédée du spécificateur
static, auquel cas elle a une liaison interne;
- toute déclaration d'un identificateur d'objet
a l'intérieur d'un bloc n'a pas de liaison sauf si le spécificateur
extern est utilisé, auquel cas elle a une liaison externe.
Ok, C'est bien joli ton truc là, mais cela veut dire quoi qu'un identificateur a une liaison externe ou interne ou n'en a pas? Et en plus quel est le rapport avec l'édition des liens?
J'y viens
Citation : C11 (n1570) § 6.2.2 al 2 p 36
In the set of translation units and libraries that constitutes an entire program, each
declaration of a particular identifier with external linkage denotes the same object or
function. Within one translation unit, each declaration of an identifier with internal
linkage denotes the same object or function.
Un identificateur disposant d'une liaison interne signifie qu'il est lié à un objet ou à une fonction définie ailleurs dans le fichier où il est déclaré.
Un identificateur disposant d'une liaison externe signifie qu'il est lié à un objet ou à une fonction définie ailleurs dans le programme.
Le boulot de l'éditeur de liens consiste donc à relier chaque identificateur avec une liaison externe ou interne à l'objet ou à la fonction correspondante. Ainsi, si je reprends mon exemple, l'éditeur de liens va relier l'identificateur déclaré dans le fichier main.c et celui défini dans le fichier test.c à la fonction définie dans le fichier test.c.
Il s'ensuit deux règles extrêmement importantes:
Citation : C11 (n1570) § 6.9 al 3 & 5 p 155
There shall be no more than one external definition for each identifier declared with internal linkage in a translation unit.
If an identifier declared with external linkage is used in an expression, [...] somewhere in the entire program there shall be exactly one external definition for the identifier.
Il ne peut exister qu'une et une seule définition d'un même identificateur avec liaison interne dans un même fichier.
Il ne peut exister qu'une et une seule définition d'un même identificateur avec liaison externe dans tout le programme.
En effet, si je prends ce code:
Secret (cliquez pour afficher)
Code : C | void
affiche(void)
{
puts("salut");
}
void
affiche(void)
{
puts("tout le monde!");
}
|
il comprend deux définitions d'un identificateur de fonction "affiche" avec liaison externe. Oui, mais à quelle fonction relie-t-on l'identificateur "affiche"? La première? La deuxième? Les deux?
Le même problème se pose si l'on précède les définitions du spécificateur
static étant donné que les définitions sont situées dans le même fichier. Ce code devient correcte si l'on place les deux définitions dans deux fichiers séparés et si on leur donne un lien interne
Les mêmes règles s'appliquent aux identificateurs d'objet, mais avec quelques subtilités. En effet, prenez ces deux fichiers:
test.c
Secret (cliquez pour afficher)
main.c
Secret (cliquez pour afficher)
Code : C | #include <stdlib.h>
int nombre;
int
main(void)
{
return EXIT_SUCCESS;
}
|
De nouveau nous avons deux définitions avec liaison externe, mais comment fait-on pour indiquer laquelle des deux est une déclaration et l'autre une définition? Et bien dans un tel cas, on indique la déclaration en la précédant du spécificateur
extern. Ainsi dans le code suivant:
test.c
Secret (cliquez pour afficher)
main.c
Secret (cliquez pour afficher)
Code : C | #include <stdlib.h>
extern int nombre;
int
main(void)
{
return EXIT_SUCCESS;
}
|
La déclaration se situe dans le fichier main.c et la définition dans le fichier test.c.
Notez que ce n'est pas parce qu'un identificateur a une liaison externe qu'il doit être déclaré en dehors de tout bloc. En effet ce code:
test.c
Secret (cliquez pour afficher)
main.c
Secret (cliquez pour afficher)
Code : C | #include <stdlib.h>
int
main(void)
{
extern int nombre;
return EXIT_SUCCESS;
}
|
revient exactement au même mis part qu'on a réduit la portée de l'identificateur "nombre" au bloc de la fonction
main.
Seul le spécificateur extern peut être utilisé pour modifié la liaison d'un identificateur d'objet ou de fonction déclaré à l'intérieur d'un bloc.
En effet dans ce code:
Secret (cliquez pour afficher)
Code : C | #include <stdlib.h>
int
main(void)
{
static int nombre;
return EXIT_SUCCESS;
}
|
le spécificateur
static modifie la durée de vie de l'objet référencé par l'identificateur "nombre" et non sa liaison.
De même, il n'est pas possible d'appliquer ce spécificateur à un identificateur de fonction déclaré au sein d'un bloc. Pourquoi? Parce que c'est explicitement interdit par la norme:
Citation : C11 (n1570) § 6.7.1 al 7 p 110
The declaration of an identifier for a function that has block scope shall have no explicit
storage-class specifier other than extern.
Ce code est donc faux:
Secret (cliquez pour afficher)
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | #include <stdio.h>
#include <stdlib.h>
int
main(void)
{
static void affiche(void);
affiche();
return EXIT_SUCCESS;
}
static void
affiche(void)
{
puts("salut");
}
|
Notez enfin que si un identificateur apparaît dans un fichier avec à la fois une liaison interne et externe, le résultat est indéterminé.
Citation : C11 (n1570) § 6.2.2 al 7 p 37
If, within a translation unit, the same identifier appears with both internal and external
linkage, the behavior is undefined.
En effet, si je prends ces deux fichiers:
test.c
Secret (cliquez pour afficher)
Code : C | #include <stdio.h>
void
affiche(void)
{
puts("salut");
}
|
main.c
Secret (cliquez pour afficher)
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | #include <stdio.h>
#include <stdlib.h>
static void affiche(void);
void affiche(void);
int
main(void)
{
affiche();
return EXIT_SUCCESS;
}
static void
affiche(void)
{
puts("tout le monde!");
}
|
L'identificateur de fonction "affiche" peut être relié à la fois à la fonction définie dans le fichier test.c et à celle définie dans le fichier main.c.