L'ajout de deux entiers 32 bits peut entraîner un dépassement d'entier:
uint64_t u64_z = u32_x + u32_y;
Ce dépassement peut être évité si l'un des entiers 32 bits est d'abord converti ou ajouté à un entier 64 bits.
uint64_t u64_z = u32_x + u64_a + u32_y;
Cependant, si le compilateur décide de réorganiser l'addition:
uint64_t u64_z = u32_x + u32_y + u64_a;
le dépassement d'entier peut encore se produire.
Les compilateurs sont-ils autorisés à effectuer une telle réorganisation ou pouvons-nous leur faire confiance pour constater l'incohérence du résultat et conserver l'ordre des expressions tel quel?
Si l'optimiseur effectue une telle réorganisation, il est toujours lié à la spécification C, de sorte qu'une telle réorganisation devient:
uint64_t u64_z = (uint64_t)u32_x + (uint64_t)u32_y + u64_a;
Raisonnement:
On commence avec
uint64_t u64_z = u32_x + u64_a + u32_y;
L'addition est effectuée de gauche à droite.
Les règles de promotion de nombre entier stipulent que, dans le premier ajout de l'expression d'origine, u32_x
être promu à uint64_t
. Dans le deuxième ajout, u32_y
sera également promu en uint64_t
.
Ainsi, pour être conforme à la spécification C, tout optimiseur doit promouvoir u32_x
et u32_y
à 64 bits non signés. Ceci équivaut à ajouter un casting. (L’optimisation n’a pas lieu au niveau C, mais j’utilise la notation C parce que c’est une notation que nous comprenons.)
Un compilateur n'est autorisé à réorganiser sous la règle comme si. C'est-à-dire que si la réorganisation donnera toujours le même résultat que l'ordre spécifié, cela est autorisé. Sinon (comme dans votre exemple), pas.
Par exemple, étant donné l'expression suivante
i32big1 - i32big2 + i32small
qui a été soigneusement construit pour soustraire les deux valeurs qui sont connues pour être grandes mais similaires, et seulement alors ajouter l'autre valeur petite (évitant ainsi tout débordement), le compilateur peut choisir de réorganiser en:
(i32small - i32big2) + i32big1
et comptez sur le fait que la plate-forme cible utilise l'arithmétique à deux compléments avec un bouclage pour éviter les problèmes. (Une telle réorganisation pourrait être judicieuse si le compilateur est pressé pour des registres et qu’il arrive que i32small
dans un registre déjà).
Il y a la règle "comme si" en C, C++ et Objective-C: le compilateur peut faire ce qu'il veut tant qu'aucun programme conforme ne peut faire la différence.
Dans ces langues, a + b + c est défini comme étant le même que (a + b) + c. Si vous pouvez faire la différence entre ceci et par exemple a + (b + c), le compilateur ne peut pas modifier l'ordre. Si vous ne pouvez pas faire la différence, le compilateur est libre de changer l'ordre, mais c'est correct, car vous ne pouvez pas faire la différence.
Dans votre exemple, avec b = 64 bits, a et c 32 bits, le compilateur serait autorisé à évaluer (b + a) + c ou même (b + c) + a, car vous ne pouviez pas faire la différence, mais pas (a + c) + b parce que vous pouvez faire la différence.
En d'autres termes, le compilateur n'est pas autorisé à faire quoi que ce soit qui rend le code différent de ce qu'il devrait être. Il n'est pas nécessaire de produire le code que vous pensez qu'il produira ou que vous pensez qu'il devrait produire, mais le code volonté vous donnera exactement les résultats qu'il devrait obtenir.
Citant les standards :
[Remarque: les opérateurs ne peuvent être regroupés selon les règles mathématiques usuelles que dans les cas où ils sont réellement associatifs ou commutatifs7. Par exemple, dans le fragment suivant, int a, b;
/∗ ... ∗/ a = a + 32760 + b + 5;
la déclaration d'expression se comporte exactement comme
a = (((a + 32760) + b) + 5);
en raison de l’associativité et de la préséance de ces opérateurs. Ainsi, le résultat de la somme (a + 32760) est ensuite ajouté à b, puis ce résultat est ajouté à 5, ce qui donne la valeur attribuée à a. Sur une machine dans laquelle les débordements génèrent une exception et dans laquelle la plage de valeurs pouvant être représentée par un entier est [-32768, + 32767], l'implémentation ne peut pas réécrire cette expression sous la forme suivante:
a = ((a + b) + 32765);
puisque si les valeurs pour a et b étaient respectivement -32754 et -15, la somme a + b produirait une exception, contrairement à l'expression d'origine; ni l'expression ne peut être réécrite soit comme
a = ((a + 32765) + b);
ou
a = (a + (b + 32765));
puisque les valeurs pour a et b pourraient avoir été respectivement 4 et -8 ou -17 et 12. Toutefois, sur une machine dans laquelle les débordements ne produisent pas d'exception et dans laquelle les résultats des débordements sont réversibles, la déclaration d'expression ci-dessus peut être réécrit par l’implémentation de l’une quelconque des manières ci-dessus car le même résultat se produira. - note de fin]
Les compilateurs sont-ils autorisés à effectuer une telle réorganisation ou pouvons-nous leur faire confiance pour constater l'incohérence du résultat et conserver l'ordre des expressions tel quel?
Le compilateur ne peut réorganiser que s'il donne le même résultat - ici, comme vous l'avez observé, ce n'est pas le cas.
Il est possible d'écrire un modèle de fonction, si vous en voulez un, qui valorise tous les arguments en std::common_type
avant d’ajouter - ce serait sûr, et ne vous fiez pas à l’argument ou au casting manuel, mais c’est plutôt maladroit.
Cela dépend de la largeur de bit de unsigned/int
.
Les 2 ci-dessous ne sont pas les mêmes (quand unsigned <= 32
morceaux). u32_x + u32_y
devient 0.
u64_a = 0; u32_x = 1; u32_y = 0xFFFFFFFF;
uint64_t u64_z = u32_x + u64_a + u32_y;
uint64_t u64_z = u32_x + u32_y + u64_a; // u32_x + u32_y carry does not add to sum.
Ils sont les mêmes (quand unsigned >= 34
morceaux). Les promotions entières ont causé u32_x + u32_y
addition à se produire en mathématiques 64 bits. L'ordre n'est pas pertinent.
C'est UB (when unsigned == 33
morceaux). Les promotions entières ont provoqué l’ajout au niveau mathématique 33 bits signé et le dépassement de capacité signé est UB.
Les compilateurs sont-ils autorisés à effectuer une telle réorganisation ...?
(Mathématiques 32 bits): Réordonner oui, mais les mêmes résultats doivent être obtenus, donc pas que propose de réordonner OP. Ci-dessous sont les mêmes
// Same
u32_x + u64_a + u32_y;
u64_a + u32_x + u32_y;
u32_x + (uint64_t) u32_y + u64_a;
...
// Same as each other below, but not the same as the 3 above.
uint64_t u64_z = u32_x + u32_y + u64_a;
uint64_t u64_z = u64_a + (u32_x + u32_y);
... pouvons-nous nous fier à eux pour constater l’incohérence du résultat et conserver l’ordre d’expression tel quel?
Faites confiance à oui, mais l’objectif de codage d’OP n’est pas clair. Devrait u32_x + u32_y
transporter contribuer? Si OP veut cette contribution, le code doit être
uint64_t u64_z = u64_a + u32_x + u32_y;
uint64_t u64_z = u32_x + u64_a + u32_y;
uint64_t u64_z = u32_x + (u32_y + u64_a);
Mais non
uint64_t u64_z = u32_x + u32_y + u64_a;