Aller au menu - Aller au contenu

Icône Les identificateurs

Avatar
Mise à jour : 13/09/2011
Difficulté : Difficile Difficile Durée d'étude : 3 jours Creative Commons BY-NC-SA
71 visites depuis 7 jours, classé 622/786
Bonjour à tous,

Le but de ce petit tutoriel est de vous expliquer en profondeur la notion d'identificateur en langage C.


Qu'est ce qu'un identificateur?


Un identificateur peut être défini comme un nom permettant de désigner, de faire référence à une entité du langage. Un exemple d'identificateur que vous connaissez bien est le nom d'une variable ou d'une fonction.


Attend, tu veux dire que tu as créé un tutoriel juste pour nous parler des noms de variable et de fonction? o_O


Oui, enfin pas tout à fait. ^^
Vous allez voir qu'un identificateur est plus qu'un simple nom, et qu'il peut désigner plus d'éléments qu'une variable ou qu'une fonction. Mais j'en ai assez dit, entrons à présent dans le vif du sujet.

Présentation

Comme je vous l'ai dit dans l'introduction, un identificateur est un nom qui permet de désigner une entité du langage. Vous savez que les variables et les fonctions font partie de ces entités, mais elles ne sont pas les seules. La Norme C11 nous en donne huit (en comptant les membres de structures/unions/énumérations à part):

Citation : C11 (n1570) § 6.2.1 al 1 p 35

An identifier can denote an object; a function; a tag or a member of a structure, union, or
enumeration; a typedef name; a label name; a macro name; or a macro parameter.


On retrouve bien les variables et les fonctions dans cette liste (les variables sont appelés des objets, nous reviendrons plus tard sur ce concept qui en fait ne correspond pas tout à fait à celui de variable). On y trouve également:

- les étiquettes de structures/unions/énumérations, qui correspondent au nom que vous donnez à votre structure/union/énumération.
- les membres de structures/unions/énumérations;
- les définitions de type (typedef);
- les étiquettes, utilisées pour l'instruction de saut goto;
- les noms de macro;
- les paramètres de macro.

Comme un exemple vaut souvent mieux qu'un long discours, voici un petit code déclarant un identificateur pour chacune des entités présentées ci-dessus:


Secret (cliquez pour afficher)

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#define identificateur_de_macro(identificateur_de_parametre_de_macro)

struct identificateur_d_etiquette_de_structure {
	identificateur_de_membre_de_structure;
};

typedef int identificateur_de_definition_de_type;

void
identificateur_de_fonction(void)
{
	int identificateur_d_objet;

identificateur_d_etiquette:
	;
}




Je ne parlerais pas des identificateurs de macro et de paramètre de macro dans la suite du tutoriel, ces derniers n'existant plus après traitement du code par le préprocesseur.

Déclaration, portée et espaces de noms

Vous avez peut-être remarqué que j'ai utilisé le terme "déclaration" dans la présentation, ce n'est pas anodin, il s'agit d'un concept fondamental du langage C permettant la création d'identificateurs.

Une déclaration déclare un identificateur, c'est à dire qu'elle le rend utilisable, visible pour la suite du programme. On dit qu'une déclaration confère une portée à l'identificateur, c'est à dire une portion du programme où il sera utilisable. Il existe quatre types de portée:

Citation : C11 (n1570) § 6.2.1 al 2 p 35

There are four kinds of scopes: function, file, block, and function prototype.


Au niveau d'une fonction Cette portée est propre aux identificateurs d'étiquettes, cela signifie que l'identificateur peut-être utilisé partout dans la fonction où il est déclaré (et ce peu importe la position de cette déclaration dans la fonction).
Au niveau d'un bloc Cela signifie que l'identificateur peut être utilisé, à partir de sa déclaration, jusqu'à la fin du bloc dans lequel il est déclaré.
Au niveau d'un fichier Cela signifie que l'identificateur peut être utilisé, à partir de sa déclaration, jusqu'à la fin du fichier dans lequel il est déclaré.
Au niveau d'un prototype Cela signifie que l'identificateur peut être utilisé, à partir de sa déclaration, jusqu'à la fin du prototype où il est déclaré.


De nouveau, voici un petit exemple déclarant plusieurs identificateurs de portées différentes:

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
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>
#include <stdlib.h>


/* L'identificateur "fonction" est visible dans tout le fichier
   car il est déclaré au début de celui-ci. */

int
fonction(void);


int
main(void)
{
	/* L'identificateur "nombre" à une portée au niveau du bloc
	   de la fonction main, il est donc uniquement utilisable dans
	   ce dernier. */
	int nombre = 20;

	goto etiquette;

	/* L'identificateur "etiquette" à une portée au niveau de la
	   fonction main, il peut donc être utilisé partout dans cette
	   dernière, peut importe l'emplacement de sa déclaration. */
etiquette:
	;

	printf("%d %d\n", nombre, fonction());
	return EXIT_SUCCESS;
}


/* L'identificateur "nombre" à une portée au niveau du fichier,
   mais n'est visible qu'à partir de sa déclaration. Seule la fonction
   "fonction" pourra l'utiliser. A noter qu'il n'y a pas de conflit avec
   l'identificateur déclaré dans le bloc de la fonction main puisqu'on
   se situe en dehors de sa portée. */
int nombre = 10;


int
fonction(void)
{
	return nombre;    
}



Une règle importante à retenir est qu'il ne peut pas exister deux identificateurs de même nom dans la même portée, a moins qu'ils ne fassent partie d'espaces de noms différents:

Citation : C11 (n1570) § 6.2.1 al 2 p 35

Different entities designated by the same identifier either have different scopes, or are in different name spaces.



C'est quoi un espace de noms? o_O


Bonne question :-°
Le concept n'est pas facile à définir, mais est par contre très facile à comprendre avec un exemple. Sachez tout d'abord qu'il existe quatre espaces de noms:

Citation : C11 (n1570) § 6.2.3 al 1 p 37

Thus, there are separate name spaces for various categories of identifiers, as follows:

-- label names (disambiguated by the syntax of the label declaration and use);
-- the tags of structures, unions, and enumerations (disambiguated by following any
of the keywords struct, union, or enum);
-- the members of structures or unions; each structure or union has a separate name
space for its members (disambiguated by the type of the expression used to access the
member via the . or -> operator);
-- all other identifiers, called ordinary identifiers (declared in ordinary declarators or as
enumeration constants).


- un dédié aux identificateurs d'étiquettes;
- un dédié aux identificateurs d'étiquettes de structures/unions/énumérations;
- un dédié aux identificateurs de membres de structures ou unions;
- un dédié à tous les autres identificateurs.

Comme promis voici un exemple:

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 <stdlib.h>


struct test {
	int test;
};


int
main(void)
{
	struct test test;

	goto test;

test:
	;

	test.test = 10;
	return EXIT_SUCCESS;
}



Comme vous le voyez le nom "test" est utilisé quatre fois et pourtant cela ne pose aucun problème, tout simplement parce que ces quatre identificateurs appartiennent à quatre espaces de noms différents. Tout risque de confusion est évité de part:

- le contexte d'utilisation de l'identificateur (l'instruction goto attend un identificateur d'étiquette);
- l'utilisation de mots-clés (struct/union/enum pour désigner l'identificateur d'étiquette de structure/union/énumération)
- l'utilisation d'opérateurs (l'opérateur . ou -> pour accéder aux membres d'une structure)
- la syntaxe de la déclaration (par exemple les deux points suivant la déclaration d'un identificateur d'étiquette).


D'accord, et si les identificateurs font partie du même espace de noms, mais ont des portées différentes?



Dans ce cas, l'identificateur ayant la portée la plus petite masquera celui ayant la portée la plus grande.



Ce n'est pas parce qu'un identificateur est déclaré à la fin d'un bloc qu'il a une portée plus petite qu'un identificateur déclaré au début de ce bloc. Dans un tel cas ils ont une portée identique, celle du bloc dans lequel ils sont déclarés.


Voici un petit code d'exemple:

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
22
23
24
25
26
27
28
#include <stdio.h>
#include <stdlib.h>


int nombre = 10;

int
main(void)
{
	/* Cet identificateur a une portée plus petite que celui déclaré
	   plus haut, il le masque donc */
	int nombre = 20;

	/* L'identificateur "nombre" fait donc référence à celui déclaré
	   dans le bloc de la fonction main */
	if (nombre == 20) {
		/* Cet identificateur a une portée plus petite que celui
		   déclaré dans le bloc de la fonction main, il le masque
		   donc */
		int nombre = 30;

		/* L'identificateur "nombre" fait donc référence à celui 
		   déclaré dans le bloc du if */
		printf("%d\n", nombre);
	}

	return EXIT_SUCCESS;
}



Dans cet exemple, il y a trois identificateurs d'objet portant tous les trois le nom "nombre". Le premier à une portée au niveau du fichier, le second au niveau du bloc de la fonction main et le troisième au niveau du bloc du if.
L'identificateur ayant une portée au niveau du fichier est donc masqué par celui ayant une portée au niveau du bloc de la fonction main qui lui-même est à son tour masqué par celui ayant une portée au niveau du bloc if. Si l'on exécute ce petit programme, il affichera donc 30.

Objet, définition et durée de vie


Présentation



Vous avez peut-être été surpris au début de ce tutoriel de me voir tout le temps parler d'objets et pas de variables comme vous y êtes habitués. La raison en est simple, il ne s'agit pas de la même chose, enfin pas tout à fait.

Citation : C11 (n1570) § 3.15 p 6

object: region of data storage in the execution environment, the contents of which can represent values


En C, un objet est une zone mémoire pouvant contenir des données. Et c'est tout, un objet n'a pas de nom et encore moins de portée, il peut par contre être référencé par un identificateur qui lui à un nom et une portée.

Maintenant que nous avons défini la notion d'objet, nous pouvons définir la notion de variable: une variable est un objet qui est référencé par un identificateur.


Attends, tu veux dire qu'il existe des objets qui ne sont pas référencés par des identificateurs? o_O


Oui, et je peux vous dire que vous en avez déjà manipulé ^^
Prenez ce code:

Secret (cliquez pour afficher)

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <stdlib.h>


int
main(void)
{
	int *test;

	test = malloc(sizeof *test);
	free(test);
	return EXIT_SUCCESS;
}



D'après vous, la zone mémoire retournée par la fonction malloc est-elle référencée par un identificateur?


Ben par l'identificateur "test", non? :euh:


Non, l'identificateur "test" désigne l'objet qui pointe sur celui alloué par la fonction malloc. L'objet alloué n'est donc pas directement accessible via un identificateur. En effet, pour y accéder, il est nécessaire d'effectuer une indirection à l'aide de l'opérateur *.


La notion de définition



Avant d'aller plus loin il faut que je vous présente une autre notion importante qui est celle de définition.

Citation : C11 (n1570) § 6.7 al 5 p 108

A definition of an identifier is a declaration for that identifier that:

-- for an object, causes storage to be reserved for that object;
-- for a function, includes the function body;


Une définition est une déclaration, mais qui en plus:

- dans le cas d'un identificateur d'objet, alloue son objet;
- dans le cas d'un identificateur de fonction, comporte le corps de la fonction.


Comme d'habitude un petit exemple illustrant cette notion:

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
22
23
24
25
26
27
28
#include <stdio.h>
#include <stdlib.h>


/* Il s'agit d'une déclaration d'un identificateur
   de fonction */ 
void affiche(void);


int
main(void)
{
	/* Il s'agit d'une définition d'un identificateur
	   d'objet */
	int test;

	affiche();
	return EXIT_SUCCESS;
}


/* Il s'agit d'une définition d'un identificateur de
   fonction */
void
affiche(void)
{
	puts("salut");
}



N'oubliez pas qu'une définition est avant tout une déclaration ;)


La notion de durée de vie



Je vous ai dit qu'un objet n'avais ni nom ni portée, il dispose cependant d'un attribut essentiel qui est sa durée de vie. La durée de vie d'un objet est la portion de l'exécution du programme durant laquelle son espace mémoire est réservé. Il existe trois types de durée de vie:

Citation : C11 (n1570) § 6.2.4 al 1 p 38

An object has a storage duration that determines its lifetime. There are four storage
durations: static, thread, automatic, and allocated.



Automatique La durée de vie de l'objet est associée au bloc dans lequel il est crée. L'objet est détruit lorsque l'on quitte ce bloc (le fait d'entrer dans un sous-bloc ou d'effectuer un appel de fonction suspend l'exécution du bloc).
Statique La durée de vie de l'objet est associée à celle du programme. L'objet est crée et initialisé une seule fois lors du lancement du programme et est détruit à la fin de celui-ci.
Locale à un thread1 La durée de vie de l'objet est associée à celle d'un thread. L'objet est crée et initialisé une seule fois lors du lancement du thread et est détruit à la fin de celui-ci.
Dynamique La durée de vie de l'objet est contrôlée par le programmeur. L'objet est crée lors d'un appel à la fonction malloc, calloc ou realloc et est détruit lors d'un appel à la fonction free.

1 Il s'agit d'un ajout de la norme C11


Toute définition en dehors de tout bloc crée un objet de durée de vie statique à moins quel le spécificateur _Thread_local ne soit utilisé, auquel cas il a une durée de vie locale à un thread.

Toute définition à l'intérieur d'un bloc crée un objet de durée de vie automatique à moins que le spécificateur static ne soit utilisé seul (auquel cas il a une durée de vie statique) ou combiné avec le spécificateur _Thread_local (auquel cas il une durée de vie locale à un thread).

Édition des liens et liaisons

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
1
2
3
4
5
6
7
8
#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? o_O


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! :pirate:

- 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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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)

Code : C
1
int nombre;




main.c



Secret (cliquez pour afficher)

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#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)

Code : C
1
int nombre;




main.c



Secret (cliquez pour afficher)

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#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.


Certains éditeurs de liens (celui fourni avec GCC pour ne citer que lui) choisissent eux-mêmes la définition dans le cas où le spécificateur extern n'est pas utilisé. Sachez cependant que cela n'est pas standard (un grand merci à Grinwik pour m'avoir corrigé sur ce point ;) ).


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)

Code : C
1
int nombre;




main.c



Secret (cliquez pour afficher)

Code : C
1
2
3
4
5
6
7
8
9
#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
1
2
3
4
5
6
7
8
9
#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
1
2
3
4
5
6
7
8
#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.

Les noms

Je vous avais dit dans l'introduction qu'un identificateur est un nom qui permet de désigner une entité du langage. Hé bien, pour ce dernier chapitre, nous allons nous attarder sur cette notion de nom.


Caractères utilisables



Un nom est composé d'une suite de lettres et de chiffres. Oui, mais quelles lettres et quels chiffres? En voici la liste exhaustive:

Citation : C11 (n1570) § 6.4.2.1 al 1 p 59

_ a b c d e f g h i j k l m
n o p q r s t u v w x y z
A B C D E F G H I J K L M
N O P Q R S T U V W X Y Z
0 1 2 3 4 5 6 7 8 9


Sachez qu'un nom ne peut pas commencer par un chiffre, il doit en effet débuter par une lettre ou par un underscore:

Citation : C11 (n1570) § 6.4.2.1 al 2 p 59

An identifier is a sequence of nondigit characters (including the underscore _, the
lowercase and uppercase Latin letters, and other characters) and digits [...]



Noms réservés par le langage



Nous savons désormais de quels caractères peuvent être composé nos noms. Cependant, tous les noms ne sont pas utilisables. En effet, certains mots sont réservés par le langage C lui-même et ne sont donc pas disponibles:


auto break case char const continue default
do double else enum extern float for
goto if inline int long register restrict
return short signed sizeof static struct switch
typedef union unsigned void volatile while _Alignas2
_Alignof2 _Atomic2 _Bool1 _Complex1 _Generic2 _Imaginary1 _Noreturn2
_Static_assert2 _Thread_local2

1 Ces mots-clés ont été ajoutés par la norme C99
2 Ces mots-clés ont été ajoutés par la norme C11


Il est à noter que certaines implémentations réservent aussi les mots asm et fortran. Il est donc également préférable de les éviter.


Noms réservés par la bibliothèque standard



À côté des noms réservés par le langage lui-même, il y a ceux réservés par la bibliothèque standard. En fait, tous les noms de fonctions (par exemple printf) ou de variables (par exemple errno) utilisés par celle-ci sont à éviter, même si vous n'incluez pas l'en-tête les utilisant. Renseignez-vous sur les différents en-têtes pour obtenir les noms qu'ils emploient ;)

En plus de cela, la bibliothèque standard réserve certains types de noms dans des portées particulières. Ainsi, sont interdits:

Citation : C11 (n1570) § 7.1.3 al 1 p 182

-- All identifiers that begin with an underscore and either an uppercase letter or another
underscore are always reserved for any use.
-- All identifiers that begin with an underscore are always reserved for use as identifiers
with file scope in both the ordinary and tag name spaces.


- Les noms commençant par un underscore et une lettre majuscule ou commençant par deux underscore et ce, peut importe leur portée;
- Les noms commençant par un underscore et ayant une portée au niveau du fichier.

Afin de bien cerner l'interdiction, voici un petit d'exemple:

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
22
#include <stdio.h>
#include <stdlib.h>

#define _HELLO    /* interdit */
#define __HELLO   /* interdit */
#define __hello   /* interdit */
#define _hello    /* interdit car il a une portée au niveau du fichier */
	

struct _structure {    /* interdit pour les même motifs */
	int _membre;   /* permis */
};


int
main(void)
{
	int _variable;   /* permis car il n'a pas une portée au niveau du fichier */
	int auto;        /* interdit car c'est un mot réservé par le langage */
	
	return EXIT_SUCCESS;
}



Notez enfin que dans le cas de l'en-tête errno.h, les noms de macro commençant par un E et un chiffre ou une lettre majuscule ne doivent pas non plus être employés, de même pour l'en-tête signal.h et les noms de macro commençant par SIG ou SIG_ et une lettre majuscule:

Citation : C11 (n1570) § 7.5 al 4 p 205

Additional macro definitions, beginning with E and a digit or E and an uppercase
letter, may also be specified by the implementation.


Citation : C11 (n1570) § 7.14 al 4 p 265

Additional signals and pointers to undeclarable functions,
with macro definitions beginning, respectively, with the letters SIG and an uppercase
letter or with SIG_ and an uppercase letter, may also be specified by the
implementation.

Q.C.M.

La portée d'un identificateur peut être modifiée à l'aide du spécificateur static ou extern.
Dans le code suivant, quelle est le type de liaison de l'identificateur d'objet "nombre"?

Code : C
1
2
3
4
5
6
7
int
fonction(void)
{
	static int nombre = 0;

	return ++nombre;
}
Le code suivant est-il correct?

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdlib.h>


struct test {
	int test;
};


typedef struct test test;


int
main(void)
{
	test test;

	goto test;

test:
	;

	test.test = 10;
	return EXIT_SUCCESS;
}
Puis-je utiliser le nom "_coordonnees" comme étiquette de la structure déclarée dans la fonction main?

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <stdlib.h>


int
main(void)
{
	struct _coordonnees {
		int x;
		int y;
	};

	return EXIT_SUCCESS;
}

Statistiques de réponses au QCM

Voilà qui termine mon exposé sur les identificateurs. J'espère que vous y voyez désormais plus clair sur cette notion et que vous jonglez avec les portées et les liaisons ;)

Partager

17 commentaires pour "Les identificateurs"
Note moyenne : 3.95 / 4 (19 votes)
Pseudo Commentaire
Hors ligne sérégon # Posté le 09/10/2011 à 11:33:41
La POO il n'y a que ca de vrai
Avatar

La vache c'est asser complexe o_O aller je me replonge, bon tuto mais n'hésite pas à nousons. prendre pour des "débiles", des fois ca devient flou dans tes exlications.

@ plus ! :)

J'ai découvert la puissance du C++ et de Qt

POUET_FOREVER et PARAZE fan ;)
 
Hors ligne Taurre # Posté le 09/10/2011 à 11:45:11
Avatar

Études : Université de Liege

Salut,

Citation : sérégon

La vache c'est asser complexe o_O aller je me replonge, bon tuto mais n'hésite pas à nousons. prendre pour des "débiles", des fois ca devient flou dans tes exlications.


Je dois reconnaître que certains passages sont assez difficile (surtout celui sur les liaisons). En général, j'ai essayé d'expliquer cela au mieux tout en restant proche de la Norme, ce qui n'est pas toujours évident (je reste parfois trop près de cette dernière). Si tu as des exemples de passages difficiles n'hésite pas, je serait ravi de t'éclaircir sur ces points et éventuellement de revoir leur écriture ;)

Pour une coloration syntaxique du C à jour :pirate:

Secret (cliquez pour afficher)

Citation : Brian W. Kernighan & Dennis M. Ritchie

In our experience, C has proven to be a pleasant, expressive, and versatile language for a wide variety of programs. It is easy to learn, and it wears well as one's experience with it grows.

 
Hors ligne Oxynory # Posté le 16/10/2011 à 22:04:09
Avatar

C'est un sujet plutôt assez difficile et ce serais bien que les explications soit un peu plus détaillées et un peu moins théorique.

Sinon c'est intéressant =)
Des petits détails qui font avancé dans l'apprentissage du langage. Merci !

Tout est une question de motivation.
 
Hors ligne SylafrsOne # Posté le 23/01/2012 à 20:24:11
Ga Bu Zo Meu
Avatar

Ville : Brunoy
Pays : France métropolitaine
Études : IUT Orsay

Juste des fautes ^^

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? Les 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ême règles s'appliquent aux identificateurs d'objet, mais avec quelques subtilités. En effet, prenez ces deux fichiers:

Image utilisateur
Image utilisateur

ce n'est jamais un bug : c'est une petite erreur.
perror(const char *) could save your life
pensez aux balises de code ! (bouton </>)
Exercices pour les débutants [C]
Message sous license WTFPL
 
Hors ligne Taurre # Posté le 23/01/2012 à 20:45:49
Avatar

Études : Université de Liege

Merci, je signales de suite aux validateurs.
NB: il y a un lien pour signaler les erreurs d'orthographe ou autre tout en bas du tutoriel ;)

Pour une coloration syntaxique du C à jour :pirate:

Secret (cliquez pour afficher)

Citation : Brian W. Kernighan & Dennis M. Ritchie

In our experience, C has proven to be a pleasant, expressive, and versatile language for a wide variety of programs. It is easy to learn, and it wears well as one's experience with it grows.

 

Voir tous les commentaires