J'ai une question assez fondamentale, mais je ne suis pas sûr de comprendre le concept ou non. Supposons que nous ayons:
int a = 1000000;
int b = 1000000;
long long c = a * b;
Lorsque j’exécute ceci, c
affiche une valeur négative, j’ai donc également changé a
et b
en long long
et tout s’est bien passé. Alors pourquoi dois-je changer a
et b
, lorsque leurs valeurs sont comprises dans la plage int
et que leur produit est attribué à c
(qui est long long
)?
J'utilise C/C++
Les int
s ne sont pas promus en long long
avant multiplication, ils restent int
s et le produit également. Le produit est alors converti en long long
, mais trop tard, un débordement a été détecté.
Avoir l’un des a
ou b
long long
devrait également fonctionner, car l’autre serait promu.
Pour les opérateurs arithmétiques, le type du résultat ne dépend pas de ce à quoi vous assignez le résultat, mais des types d'opérandes. Pour les opérateurs arithmétiques, les conversions arithmétiques usuelles sont effectuées sur les opérandes. Ceci est utilisé pour amener les opérandes à un type commun, cela signifie pour les types plus petits que unsigned / signed int si les valeurs peuvent être ajustées, elles sont promues à unsigned / signed int, dans ce cas, ils sont déjà tous les deux int, aucune conversion n'est donc requise. Voir Pourquoi un court-métrage doit-il être converti en un int avant les opérations arithmétiques en C et C++? pour les détails sur pourquoi.
Nous avons maintenant un comportement indéfini, car le dépassement d’entier signé est un comportement indéfini. C’est couvert dans le projet de section standard C++ 5
[Expr] qui dit:
Si, lors de l'évaluation d'une expression, le résultat n'est pas défini mathématiquement ou s'il ne se situe pas dans la plage de valeurs pouvant être représentées par , Le comportement n'est pas défini. [Remarque: la plupart des implémentations existantes de C++ Ignorent les débordements d'entiers. Le traitement de la division par zéro, formant un reste à l’aide d’un diviseur égal à zéro, et toutes les Exceptions en virgule flottante, varie d’une machine à l’autre et peut généralement être réglé à l’aide d’une fonction de bibliothèque. —Fin note]
De nos jours, nous avons des désinfectants pour attraper ces types de comportement indéfinis et utiliser -fsanitize=undefined
avec clang et gcc interceptera cela au moment de l'exécution avec l'erreur suivante (le voir en direct):
erreur d'exécution: dépassement d'entier signé: 1000000 * mais 1000000 ne peuvent pas être représenté dans le type 'int'
Pour la section de référence 5.6
[expr.mul] dit:
[...] Les conversions arithmétiques habituelles sont effectuées sur les opérandes Et déterminent le type du résultat.
et la section 5
dit:
Sinon, les promotions intégrales (4.5) doivent être effectuées sur les deux opérandes.61 Ensuite, les règles Suivantes doivent être appliquées aux opérandes promus.
- Si les deux opérandes ont le même type, aucune conversion supplémentaire n'est nécessaire.
C'est un peu absurde, parce que l'instruction assembler calcule toujours
int * int -> 64 bits de long
si vous regardez le code machine, vous voyez: imul qui stocke 64 bits dans eax edx puis cdq qui a placé le signe bit de eax dans edx (perdant ainsi le résultat complet de 64 bits) puis eax edx sont stockés dans la variable de 64 bits
et si vous convertissez les valeurs 32 bits en 64 bits avant la multiplication, vous obtenez un appel à la fonction de multiplication 64 bits sans raison.
(J'ai vérifié: ce n'est pas le cas lorsque le code est optimisé)