V. Grandeurs de type adresse▲
On a introduit, en décrivant les caractéristiques d'un objet,
un type particulier de valeur qui représente l'adresse d'un
objet. L'importance de ce type vient de ce que l'adresse permet
d'accéder à l'objet.
Une seule constante de ce type, NULL, est définie et
permet de représenter "l'adresse d'aucun objet".
On ne confondra pas le type adresse avec un type entier : la grandeur représentée est d'une nature différente et le sens des opérateurs est différent.
V-A. Type adresse▲
La notion d'adresse est plus riche que le simple
repérage d'une position mémoire : le type de l'objet intervient
dans la définition d'un type "adresse". Les raisons de ceci sont
les suivantes :
-
Ceci permet de savoir quel objet est concerné par
cette adresse. En effet, si un objet se situe à
une et une seule position en mémoire, l'inverse n'est pas vrai :
à une position mémoire donnée, on peut trouver
plusieurs objets de type différent : c'est le cas
si l'objet est un aggrégat (tableau ou structure) ou une
union. Par exemple, si on a un tableau, l'adresse du tableau
repère la même position mémoire que l'adresse du premier
élément du tableau. Ce sont deux objets de type différent situés
à une même position en mémoire et leur adresse sera en C
d'un type différent.
-
Ceci permet de connaître le type du résultat de
l'évaluation d'une expression comportant des
adresses (par exemple *Ta est de type
T)
-
Certains opérateurs sur les adresses utilisent
implicitement la taille de l'objet adressé, donc
doivent connaître son type.
- L'exception à ce principe concerne le type void *.
Déclaration d'un type "adresse"
Le type Ta "adresse d'un objet de type T "
sera noté en C en cohérence avec la signification de
l'opérateur unaire
* :
On écrira que l'objet à
l'adresse de type Ta, (*Ta), est
de type T : T (*Ta)
ou plus simplement :
T * Ta |
-
Si on préfère associer le signe * au type T,
T * Ta , on lira "Ta
est un type adresse de T".
-
Si on préfère voir le signe * associé au type Ta,
T * Ta, on lira "l'objet à
l'adresse de type Ta est de type T".
On retrouvera ce souci de cohérence entre déclarations de
type et action des opérateurs dans toutes les déclarations
en C ce qui explique et justifie le caractère parfois
ésotérique de la déclaration des types.
Voir à ce sujet les
déclarateurs complexes
V-B. Opérateurs sur le type adresse▲
Le type "adresse d'objet" a son jeu propre d'opérateurs ayant un sens bien particulier. Certains de ces opérateurs binaires (à deux opérandes) ont la particularité d'associer deux valeurs de types radicalement différents (adresse et entier), ce qui est une exception en C, pour fournir une valeur de type adresse.
V-B-1. Les opérateurs unaires sizeof, * et ->▲
Ces opérateurs ont été déjà décrits et on se reportera au chapitre III-A
V-B-2. Les opérateurs + et - entre une adresse et un entier ▲
Ces opérateurs sont utilisés lorsqu'on a un ensemble
d'objets d'un même type T placés consécutivement
en mémoire (autrement dit un tableau d'objets T).
Si A est l'adresse d'un objet de type T
(donc A est de type T*) et n un entier :
- A+n permet d'obtenir l'adresse du
nieme objet de type T qui serait
situé après (si n>0) ou avant (si n
<0) l'objet d'adresse A
- A-n permet d'obtenir l'adresse du nième objet de type T qui serait situé avant (si n>0) ou après (si n <0) l'objet d'adresse A
Ces opérateurs ont besoin de connaître la taille de l'objet adressé, donc son type.
V-B-3. L'opérateur []▲
Cet opérateur est dérivé de l'opérateur unaire * et de
l'opérateur + sur les adresses et permet une écriture et
une lecture plus naturelle. Cet opérateur a donc besoin de connaître
la taille de l'objet adressé, donc son type.
L'un des deux opérandes doit être une adresse A
et l'autre un entier n.
A[n] ou n[A], écriture peu courante, sont
tous deux équivalents à *(A+n)
V-B-4. L'opérateur - entre deux adresses▲
L'opérateur - est également défini entre deux adresses A et B d'un même type T* :
Si B == A+n , alors n == B-A |
Cet opérateur a besoin de connaître la taille de
l'objet adressé, donc son type.
L'opérateur + entre adresses n'est pas défini.
Ainsi, si a, b et c sont du
type T*, il est illégal d'écrire a+b-c
(équivalent à (a+b)-c)
mais correct d'écrire a+(b-c) qui est une
expression du type T*
V-B-5. Les opérateurs de comparaison▲
Ce sont les opérateurs ==, !=,
<=, <, > , >=
- Les deux premiers permettent de savoir si les adresses
sont celles d'un même objet ou de deux objets
différents.
- Les quatre derniers permettent de connaître la
position relative en mémoire de deux objets. Cette
information n'est pertinente que si ces objets
appartiennent à une même structure de données ( tableau
ou struct).
V-C. Type void *▲
Le type "adresse d'objet" spécifie normalement le type de
l'objet adressé. Il est quelquefois utile que cette
information soit, au moins provisoirement, non précisée.
Dans ce cas, le type adresse sera
"adresse d'un type void" :
void *.
Naturellement, comme le compilateur ne connait pas le type
réel de l'objet, on ne peut utiliser l'opérateur unaire
*, les opérateurs + , - portant sur
les adresses ni l'opérateur []. On ne peut
finalement pas faire grand chose avec une adresse de ce
type, à part utiliser les opérateurs de comparaison, et il
faudra, pour pouvoir utiliser les autres opérateurs,
rétablir par un transtypage explicite le type de l'adresse
en accord avec le véritable type de l'objet adressé (et
surtout ne pas se tromper !).
On a avec le type
void *
un type d'adresse s'appliquant à tous les objets. On l'appelle
quelquefois "type générique d'adresse" .
A noter que le type
void ** n'est
pas, lui, un type générique d'adresse : on connaît parfaitement
le type de l'objet adressé
(void *)
A noter que le terme void * est assez maladroitement choisi puisqu'il ne s'agit pas du tout d'une adresse de "rien" (une adresse de "rien" est représentée par la valeur NULL), mais d'une adresse (valide) d'objet dont le type est non précisé ou est la valeur NULL.
V-D. Objets pointeurs▲
Les objets permettant de stocker ce type d'information sont
appelés pointeurs.
Un pointeur est donc simplement un objet qui contient comme
information une adresse d'objet de type T. Son type
(et celui de sa valeur) est donc T*.
Outre les opérateurs s'appliquant aux adresses, les
opérateurs suivants sont définis pour les objets pointeurs :
& ++ -- ainsi que les opérateurs
d'assignation =, +=, etc. Voir le chapitre
III-A-2.
-
On ne peut donner, de façon portable, à un pointeur une
valeur entière à l'exception de 0, valeur qui se
transtype automatiquement en la valeur NULL.
On préférera toujours assigner NULL à un pointeur plutot que 0 - On exprime quelquefois, pour simplifier, le type "adresse d'un objet de type T" comme le type "pointeur sur T" et la valeur de l'adresse d'un objet de type T comme une "constante pointeur sur T" (on fera la différence avec un objet pointeur déclaré const : " pointeur constant sur T").