Structures particulières

1. Enum : définition de types personnalisés

On crée l'énumération suivante :

 
Énumération
enum semaine {
lundi,
mardi,
mercredi,
jeudi,
vendredi,
samedi,
dimanche
};
 
Équivalent
int lundi    = 0;
int mardi    = 1;
int mercredi = 2;
int jeudi    = 3;
int vendredi = 4;
int samedi   = 5;
int dimanche = 6;
 

On définit une variable jour de type semaine

enum semaine jour;
 
for (jour=lundi; jour<vendredi; jour++) printf("On travaille\n");

Comment la compilateur traite cette énumération ?

Le compilateur affecte une valeur entière à chaque élément de l'énumération (voir le tableau donnant l'équivalence). Sauf indication contraire, il commence à numéroter à partir de zéro. Puis, pour chaque nouvel élément, il incrémente la dernière valeur rencontrée (si la valeur n'est pas explicitement donnée).

Par exemple, si l'on ne veut pas que l'énumération commence à zéro mais à un. On fera :

enum semaine{ 
lundi = 1,
mardi,  
..., 
}

Les doublons de valeur sont autorisés (pas les doublons de clé)

enum pays{ 
france = 33,
royaume_uni,
etats_unis = 0,
angleterre = 34
}

Toutefois, même si les énumération sont traitées en interne comme des valeurs entières, il faudra respecter les "types".

enum semaine jour = mardi; /* correct */
 
jour = france;             /* cette affirmation est bien--sûr */
                           /* totalement erronée              */

Enumération vs Constantes symboliques.

Nous avons donné un équivalent à l'énumération semaine et on peut voir que l'on est vraiment très proche de l'utilisation des constantes symboliques. Pourtant si le résultat est quasi-identique, les mécanismes sous-jacents sont totalements différents. Les constantes symboliques sont traduites par le préprocesseur : le compilateur ne voit donc que les valeurs ... Avec les constantes symboliques, il est moins facile de contrôler le type des variables : si on avait défini jour et pays en terme de constantes symboliques, l'affectation jour = france, correcte syntaxiquement, ne serait pas signalée et l'erreur logique non détectée.

2. Union

L'union est une structure particulière : elle permet de ranger des variables différentes dans le même espace mémoire. Dans une union, on ne peut stocker qu'une valeur de champ à la fois : ainsi en mémoire, la taille de l'union est la taille du champ le plus grand en taille mémoire (et non pas la somme des tailles des champs comme pour la structure).

struct s {
int    i;
double d;
} s1;   /* déclare une structure s1 */
 
union u {
int    i;
double d;
} u1;   /* déclare une union s1     */

Pour s'en convaincre, on va vérifier les tailles respectives en mémoires et les adresses des différents champs.

printf("int %d, double %d, struct %d, union %d", sizeof(int),
   sizeof(double), sizeof(s), sizeof(u));
printf("s.d %x s.i %x, u.d %x u.i %x", &s1.d, &s1.i, &u1.d, &u1.i);

Les champs d et i de l'union sont bien stockés à la même adresse.

typedef struct { 
double rho;
double theta;
} polaire;
 
typedef struct { 
double re;
double im;
} cartesien;
 
typedef union { 
cartesien car;
polaire   pol;
} complexe;

Avantages (entre autres)

Elle permet de garder sous le même identificateur des entités qui peuvent prendre des formes différentes ( le complexe est bien le même qu'il soit donné sous forme polaire ou sous forme cartésienne).

L'union permet de ne pas gaspiller de mémoire en n'en réservant pas inutilement (On réserve de la mémoire pour une forme de complexe et non pas pour les deux formes en même temps).

Inconvénient

Il faut se "rappeler" quel champ de l'union est utilisé.

L'union est utilisée sous X-Windows pour stocker l'événement courant... La structure XEvenement couple un entier qui donne le type d'événement stocké et une structure qui contient les donnnées proprement dites de l'événement.

3. Champ de bits

Imaginons que l'on veuille créer un fichier des étudiants de l'ISIMA où l'on conserve la liste des langages maîtrisés par chaque étudiant. Une solution consiste à utiliser des variables "drapeau" ou booléennes : 0 le langage n'est pas maîtrisé, 1 le langage est maîtrisé.

struct etudiant1 {
char * nom;
unsigned short c;
unsigned short assembleur;
}

Si l'on calcule la taille de la structure, on s'aperçoit que le stockage n'est pas optimal : pour deux valeurs booléennes, on utilise deux entiers courts (chacun codé au moins sur un octet) alors qu'en fait deux bits suffiraient amplement.

On est dans un cas typique où l'on peut utiliser les champs de bits. Avec une telle structure de données, on peut spécifier le nombre de bits utiles pour contenir l'information. Le compilateur se charge d'utiliser la mémoire de manière "efficace".

struct champ_de_ bits {
type champ1 : nombre_de_bits;
type champ2 : nombre_de_bits;
...
}

Le type du champ est obligatoirement [signed]int ou unsigned int... Ainsi, pour notre petit problème, on peut créer la structure suivante :

struct etudiant2 {
char * nom;
struct langage {
unsigned int c          : 1;
unsigned int assembleur : 1;
...
}
}

Si le champ est défini sur n bits, sa valeur est comprise entre 0 et 2n-1. L'accès au champ est identique à celui d'un structure "normale".

Quelques sizeof() nous renseigneront sur le gain mémoire..

Valid XHTML 1.0!