Implicit conversions
Lorsqu'une expression est utilisée dans un contexte où une valeur d'un type différent est attendue, une conversion peut se produire :
int n = 1L; // l'expression 1L a le type long, int est attendu n = 2.1; // l'expression 2.1 a le type double, int est attendu char* p = malloc(10); // l'expression malloc(10) a le type void*, char* est attendu
Les conversions ont lieu dans les situations suivantes :
Conversion comme par affectation
- Dans l'opérateur d'assignation , la valeur de l'opérande de droite est convertie vers le type non qualifié de l'opérande de gauche.
- Dans l' initialisation scalaire , la valeur de l'expression d'initialisation est convertie vers le type non qualifié de l'objet en cours d'initialisation.
- Dans une expression d'appel de fonction , vers une fonction ayant un prototype, la valeur de chaque expression d'argument est convertie vers le type des types déclarés non qualifiés du paramètre correspondant.
- Dans une instruction return , la valeur de l'opérande de return est convertie en un objet ayant le type de retour de la fonction.
Notez que l'affectation réelle, en plus de la conversion, supprime également la plage et la précision supplémentaires des types à virgule flottante et interdit les chevauchements ; ces caractéristiques ne s'appliquent pas à la conversion comme par affectation.
Promotions d'arguments par défaut
Dans une expression d'appel de fonction lorsque l'appel est effectué vers
Chaque argument de type entier subit la promotion entière , et chaque argument de type float est implicitement converti en type double .
int add_nums(int count, ...); int sum = add_nums(2, 'c', true); // add_nums est appelée avec trois int : (2, 99, 1)
|
Notez que float complex et float imaginary ne sont pas promus en double complex et double imaginary dans ce contexte. |
(depuis C99) |
Conversions arithmétiques habituelles
Les arguments des opérateurs arithmétiques suivants subissent des conversions implicites dans le but d'obtenir le type réel commun , qui est le type dans lequel le calcul est effectué :
- arithmétique binaire * , / , % , + , - ,
- opérateurs relationnels < , > , <= , >= , == , ! = ,
- arithmétique binaire bit à bit & , ^ , | ,
- l' opérateur conditionnel ?: .
|
1)
Si un opérande a un type flottant décimal, l'autre opérande ne doit pas avoir de type flottant standard,
complexe ou imaginaire.
|
(depuis C23) |
-
- type entier ou à virgule flottante réel vers long double
| (depuis C99) |
-
- type entier ou à virgule flottante réel vers double
| (depuis C99) |
-
- type entier vers float (le seul type réel possible est float, qui reste inchangé)
| (depuis C99) |
-
- Si les types sont identiques, ce type est le type commun.
-
Sinon, les types sont différents :
- Si les types ont la même signe (signés ou non signés tous les deux), l'opérande dont le type a le rang de conversion [1] inférieur est implicitement converti [2] vers l'autre type.
-
Sinon, les opérandes ont des signes différents :
- Si le type non signé a un rang de conversion supérieur ou égal au rang du type signé, alors l'opérande avec le type signé est implicitement converti vers le type non signé.
-
Sinon, le type non signé a un
rang de conversion
inférieur au type signé :
- Si le type signé peut représenter toutes les valeurs du type non signé, alors l'opérande avec le type non signé est implicitement converti vers le type signé.
- Sinon, les deux opérandes subissent une conversion implicite vers la contrepartie non signée du type de l'opérande signé.
- ↑ Se référer aux promotions entières ci-dessous pour les règles de classement.
- ↑ Se référer aux « conversions entières » sous sémantique des conversions implicites ci-dessous.
1.f + 20000001; // int est converti en float, donnant 20000000.00 // l'addition puis l'arrondi en float donne 20000000.00 (char)'a' + 1L; // d'abord, le char 'a', qui vaut 97, est promu en int // types différents : int et long // même signe : tous deux signés // rang différent : long a un rang supérieur à int // par conséquent, int 97 est converti en long 97 // le résultat est 97 + 1 = 98 de type signed long 2u - 10; // types différents : unsigned int et signed int // signe différent // même rang // par conséquent, signed int 10 est converti en unsigned int 10 // puisque l'opération arithmétique est effectuée pour les entiers non signés // (voir le sujet "Opérateurs arithmétiques"), le calcul effectué est (2 - 10) // modulo (2 puissance n), où n est le nombre de bits de valeur de unsigned int // si unsigned int fait 32 bits de long et qu'il n'y a pas de bits de remplissage dans sa représentation objet // alors le résultat est (-8) modulo (2 puissance 32) = 4294967288 // de type unsigned int 5UL - 2ULL; // types différents : unsigned long et unsigned long long // même signe // rang différent : le rang de unsigned long long est supérieur // par conséquent, unsigned long 5 est converti en unsigned long long 5 // puisque l'opération arithmétique est effectuée pour les entiers non signés // (voir le sujet "Opérateurs arithmétiques"), // si unsigned long long fait 64 bits de long, alors // le résultat est (5 - 2) modulo (2 puissance 64) = 3 de type // unsigned long long 0UL - 1LL; // types différents : unsigned long et signed long long // signe différent // rang différent : le rang de signed long long est supérieur // si ULONG_MAX > LLONG_MAX, alors signed long long ne peut pas représenter tous // les unsigned long donc, c'est le dernier cas : les deux opérandes sont convertis // en unsigned long long unsigned long 0 est converti en unsigned long long 0 // long long 1 est converti en unsigned long long 1 puisque l'opération // arithmétique est effectuée pour les entiers non signés // (voir le sujet "Opérateurs arithmétiques"), // si unsigned long long fait 64 bits de long, alors // le calcul est (0 - 1) modulo (2 puissance 64) // ainsi, le résultat est 18446744073709551615 (ULLONG_MAX) de type // unsigned long long
|
Le type du résultat est déterminé comme suit :
double complex z = 1 + 2*I; double f = 3.0; z + f; // z reste inchangé, f est converti en double, le résultat est double complex |
(depuis C99) |
Comme toujours, le résultat d'un opérateur à virgule flottante peut avoir une plage et une précision supérieures à celles indiquées par son type (voir FLT_EVAL_METHOD ).
|
Note : les opérandes réelles et imaginaires ne sont pas implicitement converties en complexes car cela nécessiterait des calculs supplémentaires, tout en produisant des résultats indésirables dans certains cas impliquant des infinis, des NaNs et des zéros signés. Par exemple, si les réels étaient convertis en complexes, 2.0×(3.0+i∞) serait évalué comme (2.0+i0.0)×(3.0+i∞) ⇒ (2.0×3.0–0.0×∞) + i(2.0×∞+0.0×3.0) ⇒ NaN+i∞ plutôt que le résultat correct 6.0+i∞. Si les imaginaires étaient convertis en complexes, i2.0×(∞+i3.0) serait évalué comme (0.0+i2.0) × (∞+i3.0) ⇒ (0.0×∞ – 2.0×3.0) + i(0.0×3.0 + 2.0×∞) ⇒ NaN + i∞ au lieu de –6.0 + i∞. |
(depuis C99) |
Remarque : indépendamment des conversions arithmétiques habituelles, le calcul peut toujours être effectué dans un type plus étroit que celui spécifié par ces règles en vertu de la règle du "as-if" .
Transformations de valeurs
Conversion de lvalue
Toute expression lvalue de tout type non-tableau, lorsqu'elle est utilisée dans tout contexte autre que
- comme opérande de l' opérateur d'adressage (si autorisé),
- comme opérande des opérateurs de pré/post-incrémentation et décrémentation ,
- comme opérande gauche de l' opérateur d'accès membre (point),
- comme opérande gauche des opérateurs d'assignation et d'assignation composée ,
-
comme opérande de
sizeof,
subit une
conversion lvalue
: le type reste le même, mais perd
const
/
volatile
/
restrict
-qualificateurs et
atomic
propriétés, le cas échéant. La valeur reste la même, mais perd ses propriétés lvalue (l'adresse ne peut plus être prise).
Si la lvalue a un type incomplet, le comportement est indéfini.
Si la lvalue désigne un objet de durée de stockage automatique dont l'adresse n'a jamais été prise et si cet objet n'était pas initialisé (non déclaré avec un initialiseur et aucune assignation n'a été effectuée avant son utilisation), le comportement est indéfini.
Cette conversion modélise la charge mémoire de la valeur de l'objet depuis son emplacement.
volatile int n = 1; int x = n; // conversion lvalue sur n lit la valeur de n volatile int* p = &n; // pas de conversion lvalue : ne lit pas la valeur de n
Conversion de tableau en pointeur
Toute expression lvalue (jusqu'en C99) expression (depuis C99) de type tableau , lorsqu'elle est utilisée dans tout contexte autre que
- en tant qu'opérande de l' opérateur d'adressage ,
-
en tant qu'opérande de
sizeof, -
en tant qu'opérande de
typeofettypeof_unqual(depuis C23) , - en tant que littéral de chaîne utilisé pour l' initialisation de tableau ,
subit une conversion en un pointeur non-lvalue vers son premier élément.
Si le tableau a été déclaré
register
, le comportement est indéfini.
Un tableau non-lvalue, ou l'un de ses éléments , n'est pas accessible (jusqu'à C99) , a une durée de vie temporaire (depuis C99) .
int a[3], b[3][4]; int* p = a; /* conversion vers &a[0] */ int (*q)[4] = b; /* conversion vers &b[0] */ struct S { int a[1]; }; struct S f(void) { struct S result = {{0}}; /* {0} depuis C99 */ return result; } void g(void) { int* p = f().a; /* erreur jusqu'à C99 ; OK depuis C99 */ int n = f().a[0]; /* erreur jusqu'à C99 ; OK depuis C99 */ f().a[0] = 13; /* erreur jusqu'à C99 ; UB depuis C99 */ (void)p, (void)n; } int main(void) { return 0; }
Conversion de fonction en pointeur
Toute expression désignatrice de fonction, lorsqu'elle est utilisée dans tout contexte autre que
- comme opérande de l' opérateur d'adressage ,
-
comme opérande de
sizeof, -
comme opérande de
typeofettypeof_unqual(depuis C23) ,
subit une conversion en un pointeur non-lvalue vers la fonction désignée par l'expression.
int f(int); int (*p)(int) = f; // conversion vers &f (***p)(1); // déréférencement répété vers f et reconversion vers &f
Sémantique de conversion implicite
La conversion implicite, que ce soit comme par affectation ou une conversion arithmétique habituelle , consiste en deux étapes :
Types compatibles
La conversion d'une valeurs de n'importe quel type vers n'importe quel type compatible est toujours une opération neutre et ne modifie pas la représentation.
uint8_t (*a)[10]; // si uint8_t est un typedef pour unsigned char unsigned char (*b)[] = a; // alors ces types de pointeurs sont compatibles
Promotions entières
La promotion entière est la conversion implicite d'une valeur de tout type entier ayant un rang inférieur ou égal au rang de int ou d'un champ de bits de type _Bool (jusqu'en C23) bool (depuis C23) , int , signed int , unsigned int , en une valeur de type int ou unsigned int .
Si int peut représenter l'ensemble des valeurs du type d'origine (ou la plage de valeurs du champ de bits d'origine), la valeur est convertie en type int . Sinon, la valeur est convertie en unsigned int .
|
La valeur d'un champ de bits d'un type entier de précision binaire est convertie vers le type entier de précision binaire correspondant. Sinon, les types entiers de précision binaire sont exemptés des règles de promotion des entiers. |
(depuis C23) |
Les promotions d'entiers préservent la valeur, y compris le signe :
int main(void) { void f(); // déclaration de fonction de style ancien // depuis C23, void f(...) a le même comportement concernant les promotions char x = 'a'; // conversion entière de int vers char f(x); // promotion entière de char vers int } void f(x) int x; {} // la fonction attend un int
rang ci-dessus est une propriété de chaque type entier et est défini comme suit :
|
8)
le rang d'un type entier signé à précision de bits doit être supérieur au rang de tout type entier standard ayant une largeur inférieure ou de tout type entier à précision de bits ayant une largeur inférieure.
9)
le rang de tout type entier à précision de bits par rapport à un type entier étendu de même largeur est défini par l'implémentation.
|
(depuis C23) |
Note : les promotions d'entiers sont appliquées uniquement
- dans le cadre des conversions arithmétiques usuelles (voir ci-dessus),
- dans le cadre des promotions d'arguments par défaut (voir ci-dessus),
- pour l'opérande des opérateurs arithmétiques unaires + et - ,
- pour l'opérande de l'opérateur binaire unaire ~ ,
- pour les deux opérandes des opérateurs de décalage << et >> .
Conversion booléenneUne valeur de tout type scalaire peut être implicitement convertie en _Bool (jusqu'en C23) bool (depuis C23) . Les valeurs qui sont égales à une expression constante entière de valeur zéro (jusqu'en C23) sont un zéro (pour les types arithmétiques), nul (pour les types pointeur) ou ont un type nullptr_t (depuis C23) sont converties en 0 (jusqu'en C23) false (depuis C23) , toutes les autres valeurs sont converties en 1 (jusqu'en C23) true (depuis C23) . bool b1 = 0.5; // b1 == 1 (0.5 converted to int would be zero) bool b2 = 2.0*_Imaginary_I; // b2 == 1 (but converted to int would be zero) bool b3 = 0.0 + 3.0*I; // b3 == 1 (but converted to int would be zero) bool b4 = 0.0 / 0.0; // b4 == 1 (NaN does not compare equal to zero) bool b5 = nullptr; // b5 == 0 (since C23: nullptr is converted to false) |
(depuis C99) |
Conversions d'entiers
Une valeur de tout type entier peut être implicitement convertie en tout autre type entier. Sauf dans les cas couverts par les promotions et les conversions booléennes ci-dessus, les règles sont :
- si le type cible peut représenter la valeur, la valeur reste inchangée,
-
sinon, si le type cible est non signé, la valeur
2
b
, où b est le nombre de bits de valeur dans le type cible, est soustraite ou ajoutée de manière répétée à la valeur source jusqu'à ce que le résultat tienne dans le type cible. Autrement dit, les entiers non signés implémentent une arithmétique modulaire. - sinon, si le type cible est signé, le comportement est défini par l'implémentation (ce qui peut inclure le déclenchement d'un signal).
char x = 'a'; // int -> char, résultat inchangé unsigned char n = -123456; // cible est non signée, résultat est 192 (c'est-à-dire -123456+483*256) signed char m = 123456; // cible est signée, résultat est défini par l'implémentation assert(sizeof(int) > -1); // l'assertion échoue : // l'opérateur > demande la conversion de -1 en size_t, // cible est non signée, résultat est SIZE_MAX
Conversions réelles flottantes-entiers
Une valeur finie de tout type flottant réel peut être implicitement convertie en tout type entier. Sauf dans les cas couverts par la conversion booléenne ci-dessus, les règles sont :
- La partie fractionnaire est supprimée (tronquée vers zéro).
-
- Si la valeur résultante peut être représentée par le type cible, cette valeur est utilisée
- sinon, le comportement est indéfini.
int n = 3.14; // n == 3 int x = 1e10; // comportement indéfini pour un entier 32 bits
Une valeur de tout type entier peut être implicitement convertie en tout type flottant réel.
- si la valeur peut être représentée exactement par le type cible, elle reste inchangée.
- si la valeur peut être représentée, mais pas exactement, le résultat est un choix défini par l'implémentation entre la valeur immédiatement supérieure ou inférieure, bien que si l'arithmétique IEEE est supportée, l'arrondi se fait au plus près. Il n'est pas spécifié si FE_INEXACT est déclenché dans ce cas.
- si la valeur ne peut pas être représentée, le comportement est indéfini, bien que si l'arithmétique IEEE est supportée, FE_INVALID est déclenché et la valeur résultante n'est pas spécifiée.
Le résultat de cette conversion peut avoir une plage et une précision supérieures à ce qu'indique son type cible (voir FLT_EVAL_METHOD ).
Si un contrôle sur FE_INEXACT est nécessaire dans les conversions flottant-vers-entier, rint et nearbyint peuvent être utilisés.
double d = 10; // d = 10.00 float f = 20000001; // f = 20000000.00 (FE_INEXACT) float x = 1 + (long long)FLT_MAX; // comportement indéfini
Conversions des nombres réels à virgule flottante
Une valeur de tout type réel flottant peut être implicitement convertie en tout autre type réel flottant.
- Si la valeur peut être représentée exactement par le type cible, elle reste inchangée.
- Si la valeur peut être représentée, mais pas exactement, le résultat est la valeur immédiatement supérieure ou inférieure la plus proche (en d'autres termes, la direction d'arrondi est définie par l'implémentation), bien que si l'arithmétique IEEE est supportée, l'arrondi se fasse au plus près.
-
Si la valeur ne peut pas être représentée, le comportement est indéfini
.Cette section est incomplète
Raison : vérifier IEEE si une infinité de signe approprié est requise
Le résultat de cette conversion peut avoir une plage et une précision supérieures à ce qu'indique son type cible (voir FLT_EVAL_METHOD ).
double d = 0.1; // d = 0.1000000000000000055511151231257827021181583404541015625 float f = d; // f = 0.100000001490116119384765625 float x = 2 * (double)FLT_MAX; // indéfini
Conversions de types complexesUne valeur de n'importe quel type complexe peut être implicitement convertie en n'importe quel autre type complexe. La partie réelle et la partie imaginaire suivent individuellement les règles de conversion des types flottants réels. Conversions de types imaginairesUne valeur de n'importe quel type imaginaire peut être implicitement convertie en n'importe quel autre type imaginaire. La partie imaginaire suit les règles de conversion des types flottants réels. double imaginary d = 0.1*_Imaginary_I; float imaginary f = d; // f est 0.100000001490116119384765625*I Conversions réel-complexeUne valeur de n'importe quel type flottant réel peut être implicitement convertie en n'importe quel type complexe.
Une valeur de n'importe quel type complexe peut être implicitement convertie en n'importe quel type flottant réel.
Note : dans la conversion complexe-réel, un NaN dans la partie imaginaire ne se propagera pas au résultat réel. double complex z = 0.5 + 3*I; float f = z; // la partie imaginaire est ignorée, f est fixé à 0.5 z = f; // fixe z à 0.5 + 0*I Conversions réel-imaginaireUne valeur de n'importe quel type imaginaire peut être implicitement convertie en n'importe quel type réel (entier ou flottant). Le résultat est toujours un zéro positif (ou non signé), sauf lorsque le type cible est _Bool (jusqu'en C23) bool (depuis C23) , auquel cas les règles de conversion booléenne s'appliquent. Une valeur de n'importe quel type réel peut être implicitement convertie en n'importe quel type imaginaire. Le résultat est toujours un zéro imaginaire positif. double imaginary z = 3*I; bool b = z; // Conversion booléenne : fixe b à true float f = z; // Conversion réel-imaginaire : fixe f à 0.0 z = 3.14; // Conversion imaginaire-réel : fixe z à 0*_Imaginary_I Conversions complexe-imaginaireUne valeur de n'importe quel type imaginaire peut être implicitement convertie en n'importe quel type complexe.
Une valeur de n'importe quel type complexe peut être implicitement convertie en n'importe quel type imaginaire.
double imaginary z = I * (3*I); // le résultat complexe -3.0+0i perd la partie réelle // fixe z à 0*_Imaginary_I |
(depuis C99) |
Conversions de pointeurs
Un pointeur vers void peut être implicitement converti vers et depuis n'importe quel pointeur vers un type d'objet avec la sémantique suivante :
- Si un pointeur vers un objet est converti en pointeur vers void et inversement, sa valeur est égale au pointeur original.
- Aucune autre garantie n'est offerte.
int* p = malloc(10 * sizeof(int)); // malloc retourne void*
Un pointeur vers un type non qualifié peut être implicitement converti en pointeur vers la version qualifiée de ce type (en d'autres termes,
const
,
volatile
, et
restrict
peuvent être ajoutés). Le pointeur original et le résultat sont égaux en comparaison.
int n; const int* p = &n; // &n a le type int*
Toute expression constante entière constante de valeur 0 ainsi que toute expression de pointeur entier de valeur zéro convertie en type void * peut être implicitement convertie en n'importe quel type de pointeur (pointeur vers objet et pointeur vers fonction). Le résultat est la valeur de pointeur nul de son type, garantie d'être inégale à toute valeur de pointeur non nulle de ce type. Cette expression entière ou void * est appelée constante de pointeur nul et la bibliothèque standard fournit une définition de cette constante via la macro NULL .
int* p = 0; double* q = NULL;
Notes
Bien que le dépassement d'entier signé dans tout opérateur arithmétique soit un comportement indéfini, le dépassement d'un type entier signé dans une conversion d'entier est simplement un comportement non spécifié.
D'autre part, bien que le dépassement d'entier non signé dans tout opérateur arithmétique (et dans la conversion d'entier) soit une opération bien définie et suive les règles de l'arithmétique modulaire, le dépassement d'un entier non signé dans une conversion flottant-vers-entier est un comportement indéfini : les valeurs de type flottant réel qui peuvent être converties en entier non signé sont les valeurs de l'intervalle ouvert
(
-1
,
Unnn_MAX
+ 1
)
.
unsigned int n = -1.0; // comportement indéfini
Les conversions entre pointeurs et entiers (sauf d'un pointeur vers _Bool (jusqu'en C23) bool (depuis C23) et (depuis C99) d'une expression constante entière de valeur zéro vers un pointeur), entre pointeurs vers des objets (sauf lorsque l'un ou l'autre est un pointeur vers void) et les conversions entre pointeurs vers des fonctions (sauf lorsque les fonctions ont des types compatibles) ne sont jamais implicites et nécessitent un opérateur de cast .
Il n'existe aucune conversion (implicite ou explicite) entre les pointeurs vers des fonctions et les pointeurs vers des objets (incluant void * ) ou les entiers.
Références
- Norme C23 (ISO/IEC 9899:2024) :
-
- 6.3 Conversions (p: 44-50)
- Norme C17 (ISO/CEI 9899:2018) :
-
- 6.3 Conversions (p: 37-41)
- Norme C11 (ISO/IEC 9899:2011) :
-
- 6.3 Conversions (p: 50-56)
- Norme C99 (ISO/IEC 9899:1999) :
-
- 6.3 Conversions (p: 42-48)
- Norme C89/C90 (ISO/IEC 9899:1990) :
-
- 3.2 Conversions
Voir aussi
|
Documentation C++
pour
Conversions implicites
|