• Aucun résultat trouvé

B-12 - Conversion de type (\cast" operator)

II - Opérateurs et expressions II-A - Généralités

II- B-12 - Conversion de type (\cast" operator)

Opération : conversion du type d'une expression. Format :

( type2 ) exp

type2 représente un descripteur de type ; exp est une expression quelconque. L'expression ci-dessus désigne un élément de type type2 qui est le résultat de la conversion vers ce type de l'élément représenté par exp.

L'expression (type2)exp n'est pas une lvalue.

Les conversions légitimes (et utiles) sont 14 :

• Entier vers un entier plus long (ex. char ! int). Le codage de exp est étendu de telle manière que la valeur représentée soit inchangée.

• Entier vers un entier plus court (ex. long ! short). Si la valeur de exp est assez petite pour être repré- sentable dans le type de destination, sa valeur est la même après conversion. Sinon, la valeur de exp est purement et simplement tronquée (une telle troncation, sans signification abstraite, est rarement utile).

• Entier signé vers entier non signé, ou le contraire. C'est une conversion sans travail : le compilateur se borne à interpréter autrement la valeur de exp, sans effectuer aucune transformation de son codage interne.

• Flottant vers entier : la partie fractionnaire de la valeur de exp est supprimée. Par exemple, le flottant 3.14 devient l'entier 3. Attention, on peut voir cette conversion comme une réalisation de la fonction mathématique partie entière, mais uniquement pour les nombres positifs : la conversion en entier de -3.14 donne -3, non -4.

• Entier vers flottant. Sauf cas de débordement (le résultat est alors imprévisible), le flottant obtenu est celui qui approche le mieux l'entier initial. Par exemple, l'entier 123 devient le flottat 123.0.

• Adresse d'un objet de type T1 vers adresse d'un objet de type T2. C'est une conversion sans travail : le compilateur donne une autre interprétation de la valeur de exp, sans effectuer aucune transformation de son codage interne.

Danger ! Une telle conversion est entièrement placée sous la responsabilité du programmeur, le compilateur l'accepte toujours.

• Entier vers adresse d'un objet de type T. C'est encore une conversion sans travail : la valeur de exp est interprétée comme un pointeur, sans transformation de son codage interne.

Danger ! Une telle conversion est entièrement placée sous la responsabilité du programmeur. De plus, si la représentation interne de exp n'a pas la taille voulue pour un pointeur, le résultat est imprévisible.

Toutes les conversions ou l'un des types en présence, ou les deux, sont des types struct ou union sont interdites.

Note 1. Si type2 et le type de exp sont numériques, alors la conversion effectuée à l'occasion de l'évaluation de l'expression ( type2 ) exp est la même que celle qui est faite lors d'une affectation

x = expr;

ou x représente une variable de type type2.

Note 2. En toute rigueur, le fait que l'expression donnée par un opérateur de conversion de type ne soit pas une lvalue interdit des expressions qui auraient pourtant été pratiques, comme

((int) x)++; /* DANGER ! */

Cependant, certains compilateurs acceptent cette expression, la traitant comme

x = (le type de x)((int) x + 1) ;

Exemple 1. Si i et j sont deux variables entières, l'expression i / j représente leur division entière (ou euclidienne, ou encore leur quotient par défaut). Voici deux manières de ranger dans x (une variable de type float) leur quotient décimal :

x = (float) i / j; x = i / (float) j;

Et voici deux manières de se tromper (en n'obtenant que le résultat de la conversion vers le type float de leur division entière)

x = i / j; /* ERREUR */ x = (float)(i / j); /* ERREUR */

Exemple 2. Une utilisation pointue et dangereuse, mais parfois nécessaire, de l'opérateur de conversion de type entre types pointeurs consiste à s'en servir pour « voir » un espace mémoire donné par son adresse comme possédant une certaine structure alors qu'en fait il n'a pas été ainsi déclaré :

• déclaration d'une structure :

struct en_tete { long taille;

struct en_tete *suivant;

};

• déclaration d'un pointeur « générique » :

void *ptr;

• imaginez que {pour des raisons non détaillées ici{ ptr possède à un endroit donné une valeur qu'il est légitime de considérer comme l'adresse d'un objet de type struct en tete (alors que ptr n'a pas ce type-là). Voici un exemple de manipulation cet objet :

((struct en_tete *) ptr)->taille = n;

Bien entendu, une telle conversion de type est faite sous la responsabilité du programmeur, seul capable de garantir qu'à tel moment de l'exécution du programme ptr pointe bien un objet de type struct en tete.

N.B. L'appel d'une fonction bien écrite ne requiert jamais un « cast ». L'opérateur de change- ment de type est parfois nécessaire, mais son utilisation diminue toujours la qualité du programme qui l'emploie, pour une raison facile à comprendre : cet opérateur fait taire le compilateur. En effet, si expr est d'un type poin- teur et type2 est un autre type pointeur, l'expression (type2)expr est toujours acceptée par le compilateur sans le moindre avertissement. C'est donc une manière de cacher des erreurs sans les résoudre.

Exemple typique : si on a oublié de mettre en tête du programme la directive #include <stdlib.h>, l'utilisation de la fonction malloc de la bibliothèque standard soulève des critiques :

...

MACHIN *p;

...

p = malloc(sizeof(MACHIN));

...

A la compilation on a des avertissements. Par exemple (avec gcc) :

monProgramme.c: In function `main'

monProgramme.c:9:warning: assignment makes pointer from integer without a cast

On croit résoudre le problème en utilisant l'opérateur de changement de type

...

p = (MACHIN *) malloc(sizeof(MACHIN)); /* CECI NE REGLE RIEN! */

...

et il est vrai que la compilation a lieu maintenant en silence, mais le problème n'est pas résolu, il est seulement caché.

Sur un ordinateur ou les entiers et les pointeurs ont la même taille cela peut marcher, mais ailleurs la valeur rendue par malloc sera endommagée lors de son affectation à p. A ce sujet, voyez la remarque de la page 65.

Il su±t pourtant de bien lire l'avertissement a±ché par le compilateur pour trouver la solution. L'affectation p = malloc(...) lui fait dire qu'on fabrique un pointeur (p) à partir d'un entier (le résultat de malloc) sans opérateur cast. Ce qui est anormal n'est pas l'absence de cast, mais le fait que le résultat de malloc soit tenu pour un entier 15, et il n'y a aucun moyen de rendre cette affectation juste aussi longtemps que le compilateur fera une telle hypothèse fausse.

La solution est donc d'informer ce dernier à propos du type de malloc, en faisant précéder l'affectation litigieuse soit d'une déclaration de cette fonction

void *malloc (size_t);

soit, c'est mieux, d'une directive ad hoc :

#include <stdlib.h>

...

p = malloc(sizeof(MACHIN));

...

*

14 Attention, il y a quelque chose de trompeur dans la phrase « conversion de exp ». N'oubliez pas que, contrairement à ce que suggère une certaine manière de parler, l'évaluation de l'expression (type)exp ne change ni le type ni la valeur de exp.

*

15 Rappelez-vous que toute fonction appelée et non déclarée est supposée de type int.