Menu

Le transtypage en C : conversions implicites et casts explicites

Le C convertit entre types en permanence - parfois parce que vous l'avez demandé avec un cast, et plus souvent tout seul. Voici les règles de promotion, la syntaxe du cast explicite, et les conversions qui perdent silencieusement des données.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

Le C convertit les valeurs entre types en permanence. Certaines de ces conversions, c'est vous qui les écrivez avec un cast ; la plupart, le compilateur les effectue en silence selon des règles que vous n'avez pas choisies. Savoir lesquelles sont lesquelles fait la différence entre « pourquoi ma moyenne vaut-elle toujours 3 ? » et du code qui fait ce qu'il dit.

La conversion implicite

Dès qu'une valeur d'un type rencontre un contexte en attendant un autre, le C la convertit :

Les conversions qui ne peuvent pas perdre d'information (int vers double, char vers int, short vers long) sont élargissantes et toujours sûres. Les conversions dans l'autre sens sont rétrécissantes et peuvent perdre des données - le 3.9 ci-dessus est devenu 3 sans aucun avertissement, sauf si vous en demandez un avec -Wconversion.

Les conversions arithmétiques usuelles

Quand un opérateur binaire a des opérandes de types différents, le C les convertit vers un type commun avant de travailler. L'échelle, de bas en haut :

  1. Tout ce qui est plus petit qu'un int (char, short, _Bool) est promu en int. C'est la promotion entière et elle a lieu en premier, toujours.
  2. Si l'un des côtés est long double, l'autre devient long double.
  3. Sinon, si l'un est double, l'autre devient double.
  4. Sinon, si l'un est float, l'autre devient float.
  5. Sinon, parmi les types entiers, celui de rang le plus élevé l'emporte, et à rang égal, le non signé l'emporte.

Cette dernière règle est celle qui cause de vrais bugs. Les autres sont intuitives.

La promotion entière explique pourquoi l'arithmétique sur char ne déborde pas comme on pourrait s'y attendre - et pourquoi restocker le résultat dans un char, si.

Les casts explicites

Un cast, c'est le type cible entre parenthèses :

(double)x
(int)3.9
(char)65
(unsigned int)n

Il s'applique à l'expression qui le suit immédiatement, et s'attache très fort - plus fort que *, / ou +.

La première ligne divise en entiers (donnant 3) puis convertit 3 en 3.0 - trop tard. La seconde convertit total avant la division, donc l'opérateur / voit un double et un int, promeut l'int, et effectue une division flottante.

Convertir un seul opérande suffit. Les conversions arithmétiques usuelles s'occupent de l'autre.

Corriger la division entière

C'est la raison la plus courante d'écrire un cast en C :

La ligne du pourcentage est instructive : passed / n vaut 3 / 5, soit 0 en entiers, et 0 * 100 vaut 0. Multiplier avant de diviser (100 * passed / n) corrige cela même sans cast, car 300 / 5 est exact - mais cela ne fonctionne que si les nombres coopèrent. Le cast est la correction fiable.

Troncature, pas arrondi

Convertir une valeur flottante en entier jette la fraction. Cela tronque vers zéro - cela n'arrondit pas :

Si vous compilez cela sur votre propre machine, rappelez-vous que math.h demande -lm à l'édition de liens sous Linux.

Un autre danger : convertir une valeur flottante trop grande pour le type entier est un comportement indéfini, pas un rebouclage. (int)1e20 peut produire n'importe quoi. Vérifiez la plage avant de convertir quand la valeur n'est pas sous votre contrôle.

char et int

Un char en C est un petit entier contenant un code de caractère. Convertir entre les deux est un travail quotidien :

digit - '0' est l'idiome standard pour transformer un caractère de chiffre en sa valeur, et cela fonctionne car les dix caractères de chiffres sont garantis consécutifs. Pour les lettres, préférez toupper() et tolower() de ctype.h à l'arithmétique + 32 - le décalage est un fait ASCII, pas une garantie du C.

Un piège voisin : les fonctions de ctype.h comme isdigit et toupper prennent un int qui doit être soit EOF, soit représentable en unsigned char. Passer un char simple négatif (possible, puisque char peut être signé) est indéfini. Convertissez-le : isdigit((unsigned char)c).

Le piège signé/non signé

L'étape 5 de l'échelle de conversion - le non signé l'emporte à rang égal - produit la comparaison la plus surprenante du C :

-1 est converti en unsigned int, ce qui réinterprète son motif de bits comme 4 294 967 295. C'est plus grand que 1, donc la comparaison est fausse.

La même conversion fait tourner les boucles indéfiniment :

/* BUG: i est non signe, donc i >= 0 est toujours vrai. Quand i vaut 0, i-- reboucle. */
for (size_t i = n - 1; i >= 0; i--) { ... }

Et elle fait échouer les vérifications de longueur :

/* BUG: strlen renvoie un size_t (non signe). Si la chaine fait moins
   de 5 caracteres, len - 5 reboucle vers un nombre enorme et le test passe. */
if (strlen(s) - 5 > 0) { ... }

Réécrivez if (strlen(s) > 5) et la soustraction n'a jamais lieu.

Les défenses : gardez les comptes et les indices dans une seule signature partout, compilez avec -Wsign-compare (inclus dans -Wextra), et quand vous devez mélanger, convertissez explicitement après avoir établi que la valeur ne peut pas être négative.

Convertir des pointeurs

Les casts convertissent aussi entre types de pointeurs, et là ils portent un vrai risque, car ils changent la façon dont la mémoire est interprétée, pas les octets eux-mêmes.

Sur une machine petit-boutiste, cela affiche 01 00 00 00. Inspecter la représentation d'un objet à travers un unsigned char * est l'un des rares casts de pointeurs que la norme bénit explicitement.

La plupart des autres casts de pointeurs ne sont pas bénis. Lire un int à travers un float * viole la règle d'aliasing strict et est un comportement indéfini même si cela compile ; utilisez plutôt memcpy pour réinterpréter des octets.

Deux conventions à connaître. void * se convertit vers et depuis tout type de pointeur d'objet sans cast en C, c'est pourquoi vous ne devriez pas convertir le résultat de malloc :

int *arr = malloc(n * sizeof *arr);        /* du C correct */
int *arr = (int *)malloc(n * sizeof *arr); /* inutile ; cache un en-tete manquant */

Le cast est obligatoire en C++, c'est pourquoi tant de code le porte. En C, il peut masquer l'erreur d'avoir oublié <stdlib.h>.

Et printf("%p", ...) attend un void *, donc les arguments pointeurs y ont réellement besoin d'un cast : printf("%p", (void *)p).

Quand un cast est la mauvaise réponse

Un cast fait taire le compilateur. Parfois le compilateur avait raison.

long big = 5000000000L;
int  small = (int)big;    /* le cast cache une vraie perte de donnees */

Si la valeur tient réellement, le cast documente que vous avez vérifié. Si ce n'est pas certain, le cast a transformé un avertissement en mauvaise réponse silencieuse. Avant d'en écrire un, demandez-vous si la solution n'est pas plutôt de changer le type d'une variable - double plutôt qu'int, size_t plutôt qu'int, long long plutôt que long. Un cast est le bon outil surtout quand deux types corrects doivent se rencontrer pour une opération, comme dans (double)sum / count.

Questions fréquentes

Comment fait-on un cast en C ?

Mettez le type cible entre parenthèses avant la valeur : (double)x, (int)3.9, (char)65. Le cast s'applique à l'expression qui le suit immédiatement, donc (double)a / b convertit a d'abord puis divise, alors que (double)(a / b) divise en entiers et convertit le résultat.

Comment convertir un int en float en C ?

L'affectation le fait implicitement - double d = 5; stocke 5.0. Dans une expression, il vous faut souvent un cast explicite : (double)total / count force une division flottante au lieu d'une division entière.

Que se passe-t-il quand on convertit un float en int en C ?

La partie fractionnaire est jetée - tronquée vers zéro, jamais arrondie. (int)3.9 vaut 3 et (int)-3.9 vaut -3. Pour arrondir, ajoutez 0,5 avant le cast pour les positifs, ou utilisez round(), floor() ou ceil() de math.h.

Pourquoi comparer un int signé et un non signé donne-t-il une mauvaise réponse ?

Les conversions arithmétiques usuelles du C convertissent la valeur signée en non signée, donc -1 < 1u est faux : -1 devient un nombre positif énorme. Gardez les comptes et les tailles dans une seule signature, ou convertissez explicitement après avoir vérifié que la valeur ne peut pas être négative.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER