Bonjour,
Je voulais comment détecter un overflow sur une opération sur des int.
Je ne code pas, c'est juste une interrogation.
Version imprimable
Bonjour,
Je voulais comment détecter un overflow sur une opération sur des int.
Je ne code pas, c'est juste une interrogation.
Bonjour
Désolé on ne peut pas. (unsigned short)65535 + 1 donnera 0 sans que tu ne puisses détecter si c'est normal ou pas. Enfin le terme "normal" est mal utilisé car c'est en réalité tout à fait normal (le "1" qui se trouve sur le 33° bit n'est pas récupéré) mais tu ne peux pas détecter si la valeur officielle devait être 65536 ou 0.
Merci pour le retour.
C'était ce qu'il me semblait avoir compris, mais j’avais l'impression de comprendre de travers.
Surprenant qu'on ne puisse détecter un overflow, je trouve même ça problématique.
C'est tiré de la conception du C: son but clairement défini est d'aller le plus vite possible. Et pour cela il ne vérifie rien, absolument rien. Je peux écrire char *pt=(char*)123 puis aller voir printf("%d", *pt) je n'aurai aucun souci de compilation.
C'est effectivement problématique pour ceux qui sont habitués à travailler avec un langage rempli de garde-corps.
:mrgreen: bof, c'est surtout à l'exécution que le problème intervient.
L'exemple de @Sve@r est assez simple, et n'est sûrement pas représentatif des "overflows".
Le compilateur peut faire la tâche mais pour combien de problèmes ? 2% ? 20% ? 50% ? 70% ? 92% ?
Et c'est l'histoire du vol 501 d'Ariane 5 (<- lien Wikipédia) "les valeurs trop élevées mesurées par les accéléromètres ont provoqué un dépassement de capacité"
Le fait qu'un exemple soit simple est souvent une bonne chose, non ? :P
C'est vrai, il se voulait plutôt représentatif des erreurs d'adressage non détectées et qui provoquent un UB. Parce que pour l'overflow effectivement, sauf à "voir" que la valeur finale est plus petite que la valeur d'origine (on présume que celle-ci est censée croître dans l'opération) il n'y a pas d'erreur à l'exécution (ni même de UB).
Un de mes profs d'info avait parlé de sinus négatif calculé en unsigned (légende urbaine issue du téléphone arabe probablement) et le lien wiki est plus précis. Ou alors il parlait d'un autre crash...
Les languages C et C++ ne vérifient pas les dépassements de capacités des types fondamentaux. D'autres languages vont prévenir avec par exemple une exception. Mais est-ce vraiment mieux?
Le code peut difficilement "corriger" le problème et aboutit finalement à un arrêt du programme.
Si on prend le cas du vol 501 d'Ariane, il y aurait eu signalement immédiat de l'anomalie (au lieu d'une anomalie détectée indirectement) pour arriver finalement au même problème :roll:
Justement, je connais le problème de l'Ariane. A l'époque, ma société participait à la qualification de modules d'Ariane (pas celui incriminé ici). Et pour préciser ce que dit Wikipédia, l'évolution du matériel a bien été requalifiée. On a eu alors des précisions en particulier sur le contrôle calibration indiqué évidemment comme non actif pendant le vol. En fait les résultats n'étaient pas utilisés, mais ses erreurs, elles, continuaient de remonter.
Le principal problème est, selon moi, l'ambiguïté sur "le contrôle calibration est désactivé" (est-ce un retrait total, la non utilisation des résultats, les erreurs traitées même si l'utilisation est désactivée?)
Il y eu une cause additionnelle non citée dans l'article. La préparation a duré plus que prévu, et la température du comburant était à la limite supérieure, d'où une fluidité plus grande, d'où une poussée un peu supérieure à l'attendu. Peut-être que si on avait fait moins de pré-contrôles, on n'aurait pas eu le problème :)
Bonjour,
Par rapport à la question initiale, n'y a t-il pas un moyen avec les registres CPU (donc en assembleur ?). De mémoire, je pensais que la CPU allait mettre un flag à 1, en cas d'overflow.
https://wiki.sei.cmu.edu/confluence/...lt+in+overflow
https://gcc.gnu.org/onlinedocs/gcc/I...-Builtins.htmlCode:
1
2
3
4
5
6
7
8
9
10
11
12 #include <limits.h> void f(signed int si_a, signed int si_b) { signed int sum; if (((si_b > 0) && (si_a > (INT_MAX - si_b))) || ((si_b < 0) && (si_a < (INT_MIN - si_b)))) { /* Handle error */ } else { sum = si_a + si_b; } /* ... */ }
non ?Code:The compiler will attempt to use hardware instructions to implement these built-in functions where possible, like conditional jump on overflow after addition, conditional jump on carry etc.
Je me suis posé la même question, il y a un bit dans un registre de la CPU qui est positionné en cas de dépassement, c'est l'overflow flag. Mais je ne sais pas si c'est pertinent dans cette discussion.
Apparemment des fonctions internes à gcc présentes dans le lien d'unanonyme permettent de gérer l'overflow, mais je pense que ce n'est pas standard.Citation:
Uniquement si on peut le récupérer en C
Ca n'est pas standard, je pense que c'est parce que ça nécessite d'utiliser les bits spécifiques à chaque processeur (carry et overflow) et que certains processeur pouvaient ne pas avoir. C'est faisable sans, mais lourd pour la multiplication.
Mais la plupart des compilateurs ont bien implémentés des fonctions intrinsèques pour cela.
Et le C++ semble avoir décidé que cette contrainte n'existe plus et fournit depuis C++26 : std::add_sat() std::sub_sat() std::mul_sat() std::div_sat() et std::saturate_cast<>(), ça devrait vraisemblablement aussi évoluer en C.
Pour une opération d'addition que tu sais peux overflow, tu peux manuellement la prévenir - dans une certaine mesure.
Au lieu d'ajouter A + B puis vérifier si ça a dépassé, tu vérifies d'abord que MAX - A > B
Bonjour,
La plupart des langages compilés permettent d'inclure des instructions assembleurs et les CPU ont toutes une collections de flags qui se positionnent à la moindre opération classique (entendre pas SIMD). C'est donc possible.
Mais ce n'est pas nécessairement souhaitable. Par exemple, si on doit vérifier que la différence entre 2 unsigned, Tnow - Tstart, reste en deçà d'une valeur dT, il est intéressant de pouvoir écrire if(Tnow - Tstart < dT) sans se préoccuper d'un overflow (il faut quand même que ce test soit réitéré avec une fréquence compatible avec la taille des unsigned utilisés).
Les options saturate ne corrigent pas le problème d'overflow mais le transforment : max + 1 = 0 devient max + 1 = max. En général pour l'implémenter à l'économie on utilise les instructions SIMD qui prévoient ce type d'opérations (ainsi qu'un certains nombre d'autres intéressantes, la plupart également induites par l'absence de tests intégrés difficiles à implémenter sur des vecteurs).
Salutations
Le pire, c'est que pour des entiers signés, l'overflow est un comportement indéfini, tu ne peux donc même pas tester après coup le résultat: Si la branche actuelle du code contient un calcul qui fait une overflow, tu as déjà perdu, game over.
Heureusement, ce n'est pas le cas des entiers non-signés, donc dans certains cas tu peux faire des tests, du genre:
Malheureusement ceci est impossible avec des entiers signés, et je ne vois pas trop comment tester qu'une opération ne va pas causer d'overflow dans le cas de la multiplication.Code:
1
2
3
4
5
6
7
8
9 unsigned int multiplier(unsigned int a, unsigned int b) { unsigned int res = a*b; if(res/a != b || res/b != a) { //Oups! } return res; }
Diviser INT_MAX par la valeur absolue de chaque opérande et comparer à l'autre? (en supposant qu'aucune opérande ne peut valoir INT_MIN, qui n'a pas de valeur absolue -- d'un autre côté une multiplication de INT_MIN par n'importe quelle valeur autre que 0 ou 1 est garantie causer une overflow)
Bonjour,
La multiplication doit avoir un format d'accueil deux fois plus longs que les opérandes. Même en 32 bits le résultat d'une multiplication était déjà sur 64 bits (edx:eax) ce qui évite sur les signés (et les non signé) tous les dépassements y compris pour le -231 qu'il convient d'éviter pour d'autres raisons (relative indétermination -imin = imin par exemple).
Le C est assez permissif pour permettre d'écrire un truc comme une multiplication de 2 entiers de 64 bits à ranger dans un octet. La multiplication se fera bien dans les règles de l'art en étendant les 64 bits à 128 bits par concaténation des registres rdx et rax donc sans risque d'overflow mais le casting sauvage qui s'en suivra fera une troncature classique mais désastreuse sans remord ni overflow.Code:imul edx = edx*eax résultat dans edx:eax
En résumé si on veut mettre les deux pieds dans le même soulier, il y a intérêt à changer de pointure 8-)
Salutations
Ben en fait non ^^
Par exemple RISC-V n'a pas de register flag.
(Je crois que le MIPS aussi mais pas sur).
Le soucis du flag register sur les processeurs moderne, c'est qu'ils empêchent certaine optimisation (où plutôt les rend horriblement compliqué), du coup les réduire à certaine instruction (cmp) ou les virer comme le fait RISC-V rend le design du processeur plus simple et plus facilement optimisable.
Donc il est probable que dans l'avenir ce genre de flag register disparaisse sur les nouvelles conceptions d'ISA.
Et comment tu fais pour vérifier une retenue ? un dépassement ?
Bonjour Kannagi,
RISC comme MIPS ont des registres de flags explicites pour les opérations flottantes (FCRS...).
Mais pour les entiers; ils utilisent des opérations de branchement conditionnel intégrant comparaison et décision de branchement. C'est plus simple mais ne permet pas l'anticipation. En effet, avoir des décisions conditionnelles qui font à la fois la comparaison et l'exploitation de celle-ci pour un branchement (ou autre) oblige à ne connaître le branchement effectif qu'au dernier moment.
Ceci étant, comme la plupart des instructions CISC modifient les flags, il est difficile de faire une comparaison quelques opérations avant le branchement conditionnel en résultant. Pour favoriser les optimisations, il faudrait plutôt multiplier (2 ou 4 devraient suffire) les registres de flags ce qui permettrait l'anticipation (et non seulement la prédiction) du branchement effectif quelques opérations plus loin (genre cmp ri, rj, rfk pour registre de flag k suivi, quelques opérations plus loin, d'un jcxx addr, rfk).
Certains pensent qu'il n'y a pas de pipe sur les machines RISC (une instruction = un tick, même pour * ou / ?). C'était vrai au tout début lorsque le jeu d'instructions était vraiment réduit mais les RISC d'aujourd'hui ont bien plus plus d'instructions que les CISC d'hier. Donc le besoin de pipe réapparaît et son optimisation également.
Salut
Pour chrtophe :
Sur le net , on propose pour le carry cette solution :
Ce qui donne le code assembleur suivant :Code:
1
2
3
4
5
6 unsigned carry_out_manual(unsigned a, unsigned b) { unsigned sum = a+b; return sum < a; }
Du coup j'imagine que y'a moyen de recuperer le NZCV par diverse astuce.Code:
1
2
3
4not a0, a0 sltu a0, a0, a1 ret
Il est vrai que sur x86, il n'ya pas d'équivalent à sltu (alors qu'il existe sur MIPS aussi ).
Bonjour , exactement celà oblige pour les branchements à les connaître au dernier moment, mais aussi de vérifier que chaque instruction ne sont pas dépendant entre elle niveau flag (comme adc).Citation:
Mais pour les entiers; ils utilisent des opérations de branchement conditionnel intégrant comparaison et décision de branchement. C'est plus simple mais ne permet pas l'anticipation. En effet, avoir des décisions conditionnelles qui font à la fois la comparaison et l'exploitation de celle-ci pour un branchement (ou autre) oblige à ne connaître le branchement effectif qu'au dernier moment.
Celà n'a absolument rien n'a voir avec le nombre d'instruction, le ARM quand il est apparu avait un pipe de 3 étages, et le MIPS de 5 étages.Citation:
Certains pensent qu'il n'y a pas de pipe sur les machines RISC (une instruction = un tick, même pour * ou / ?). C'était vrai au tout début lorsque le jeu d'instructions était vraiment réduit mais les RISC d'aujourd'hui ont bien plus plus d'instructions que les CISC d'hier. Donc le besoin de pipe réapparaît et son optimisation également.
C'est plutôt le contraire, les processeur RISC se pipeline bien comparé aux CISC.
Disons que les besoins d'optimisation actuel (comparé aux années 80/90) ont changé, en in order avoir un register flag est pas gênant, et avant on était seulement en in order.
Mais vu que pour les hautes performances on utilise que du Out of order, les processeurs actuel sont pensée pour celui là.
Bonjour Kannagi,
Ce code asm est partiel, l'addition n'y figue pas. En x86, le code complet ressemblera à un truc du genre : add eax, edx; setc al; ret;
Donc pas d'anticipation. L'exemple adc sur les dépendances flag qu'il faudrait vérifier pour chaque instruction montre le contraire. adc est une variante de l'addition qui reprend le carry (d'où le c) pour cascader l'addition (BigInt notamment). Si cette extension n'est pas l'objectif, on utilise add sans dépendance flag. C'est donc une dépendance affichée et choisie.Citation:
...exactement celà oblige pour les branchements à les connaître au dernier moment, mais aussi de vérifier que chaque instruction ne sont pas dépendant entre elle niveau flag (comme adc)...
Plus le jeu d'instructions grandit, plus il y a des instructions complexes qui ne peuvent s'exécuter en un seul cycle. Le pipe, en découpant les instructions en micro-ops, permet de maintenir un flux de traitement élevé malgré cela. C'est pourquoi le nombre d'étages ne cesse de croître dans toutes les architectures.Citation:
...Celà n'a absolument rien n'a voir avec le nombre d'instruction, le ARM quand il est apparu avait un pipe de 3 étages, et le MIPS de 5 étages.
C'est plutôt le contraire, les processeur RISC se pipeline bien comparé aux CISC.
Croire qu'un registre de flags crée des dépendances est faux. Les dépendances aux flags sont limitées et explicites comme le c de adc. En revanche add ne dépend en rien des flags positionnées par les opérations précédentes. En résumé, le out of order n'est pas impliqué par l'existence d'un registre de flags. Sinon, pourquoi les RISC les auraient reconduits (fscr...) pour les instructions flottantes ?Citation:
Disons que les besoins d'optimisation actuel (comparé aux années 80/90) ont changé, en in order avoir un register flag est pas gênant, et avant on était seulement en in order.
Mais vu que pour les hautes performances on utilise que du Out of order, les processeurs actuel sont pensée pour celui là
En revanche, si nous avions plusieurs registres indexés de flags, le out of order ne serait même plus contraint par ces dépendances choisies. Mais peut être existent-ils déjà des registres de flags fantômes comme il existe des registres classiques fantômes. Même si cela est, je préférerais une gestion explicite accessible par le compilateur plutôt qu'une optimisation locale à la CPU.
Salut
Bonjour, ah ben on est pas du tout d'accord :)
Ah non , c'est quasi impossible de faire toute les instructions en 1 cycle même avec que des instructions simple (sans baisser drastiquement la fréquence), et je parle même d'un simple add là ;).
On peut avoir des instructions plus complexe (SIMD, des load/store complexe) sans avoir des pipelines grandes.
Si les pipelines grandissent ,ce n'est pas forcément à cause des instructions complexe, mais que le OoO demande un pipeline bien plus grande, en in order un proc RISC une pipe de 5-6 étages suffit que ça soit MIPS/PowerPC/ARM ou autre ;)
Bref il n'y a aucun lien entre instruction complexe et pipeline.
Le pipeline est né avec les premiers ARM et MIPS, il n'ont jamais eu aucun "pipeline".
Mais avec 3 étages par exemple , il n'y a pas de pipeline stall, donc c'est comme si tout se faisait en 1 cycle.
Ce n'est pas une croyance, si tu il y'a des instructions adc, il y'a dépendance (tu ne peux pas exécuter dans le désordre), bien sur que c'est choisis par le compilo ou le programmeur, mais la CPU qu'est qu'elle en sait que tu vas pas l'utiliser ?
Nullement, donc il est obligé de vérifier au cas où.
Et si y'a dépendance, alors il est obligé soit de réorganise en prenant en compte cette dépendance et/ou d'avoir un renaming register pour les flags.
Si j'ai bien compris, avec un RISC, on utilise des instructions d'additions avec branchement conditionnels. J'en conclus que la CPU doit avoir des flags en interne mais non accessibles.
De toute façon au niveau C, c'est la même chose, on utilise des fonctions spécifiques au compilateur.
Bonjour Kannagi,
Il faut que deux pierres se heurtent pour que jaillisse l'étincelle. Ca tombe bien, j'ai la tête dure :D
Extrait
Aujourd'hui les opérations le plus simples s'exécutent en un cycle (1 µop dans le pipe). Il y a eu une époque où un décalage (<< ou >>) prenait plusieurs cycles, puis un seul pour un décalage unitaire, et enfin un seul pour tout décalage (registre à registre).
Instruction Operands Cycles Latency ADD, SUB r, r/i 1 1
Mais dans un même temps le nombre d'opérations a cru. Et les nouvelles n'étaient pas aussi simples. Egalement dans ce même temps la largeur des opérandes est passé de 8, 16, 32, 64 et plus. Or certaines opérations aussi peu exotiques que la multiplication ou la division présentent une forte dépendance temps à la taille des opérandes. Tout cela augmente le besoin en étages de pipe.
Mais ce n'est effectivement pas le seul moteur, la multiplication des unités (ALU et autres) au sein d'un même core induit une possibilité de // qui démultiplie les besoins en niveaux de pipe (beaucoup plus que le simple réagencement de séquences d'instructions (OoO)). Les instructions vectorielles participent aussi à cette croissance mais leur contribution semble modérée.
Aujourd'hui on se balade entre 7 et plus d'une trentaine d'étages.
adc est un code qui utilise par définition le flag carry (il n'y a nulle incertitude). Comment la CPU le devine-t-elle ? Le code adc est différent du code add qui lui ne dépend pas des flags. Il n'a donc rien à vérifier, la CPU sait d'emblée que adc dépend de c et il connait la dernière opération qui modifie ce flag. C'est une dépendance comme celle qui existe entre not a0, a0 et sltu a0, a0, a1 (réutilisation de résultat). La dépendance limite la réorganisation du code mais ne la supprime pas. Si le programmeur ou le compilateur utilise adc, c'est qu'il a besoin de tenir compte d'une retenue provenant d'un calcul préalable. Cette dépendance se manifestera toujours quelque soit l'architecture. Comme il fallait avoir fait l'addition des unités pour pouvoir faire celle de dizaines puis... :mouarf:Citation:
...si tu il y'a des instructions adc, il y'a dépendance (tu ne peux pas exécuter dans le désordre), bien sur que c'est choisis par le compilo ou le programmeur, mais la CPU qu'est qu'elle en sait que tu vas pas l'utiliser ?
Salut
Ce n'est pas forcément le bon topic pour débattre , mais oui dans ton screen add/sub , se fait en un cycle , ça n’emperche pas d'avoir un pipe de 5 étages ou plus .
Sinon tu parle "dans un temps ne t’inquiète pas je connais assez bien le temps de cycle du 6502,z80 ,8086 , M68000 et consort.
Comme j'ai pu faire aussi du MIPS/RISC-V / ARMet compagnie ^^
Quand je parle qu'une instruction fasse un cycle , je ne parle pas de son temps d’exécution (qui fait bien un cycle) mais de l'ensemble qui lui ne fais pas un cycle, que ça soit aujourd'hui ou dans le passé.
Le pipeline divise juste les instructions en plusieurs cycle pour les exécuter en parallèle, ce qui donne l'impression que chaque instruction fasse un cycle ;)
(Je ne parle pas du supersclaire au cas où).
Bref pour dire que la complexité d'instruction joue peu sur le pipeline,mais si on monte à 30 étages , c'est pas vraiment pour la complexité des instructions , mais que le OoO c'est minimun 12 étages.
Bonjour,
Il existe des options de compilation de GCC permettant de récupérer les flags, cf :
https://gcc.gnu.org/onlinedocs/gcc-3...0Gen%20Options
-ftrapv
This option generates traps for signed overflow on addition, subtraction, multiplication operations.
-fwrapv
This option instructs the compiler to assume that signed arithmetic overflow of addition, subtraction and multiplication wraps around using twos-complement representation. This flag enables some optimizations and disables other. This option is enabled by default for the Java front-end, as required by the Java language specification.
Comme souvent tout est dans la doc ;-)
Bonne journée
Bonjour chrtophe,
Je pense qu'il faudrait toujours détecter ce genre d'erreur la qualité du logiciel ne peut qu'en être améliorée.
Je complète mon commentaire avec un premier exemple qui traite le débordement d'entier en utilisant :
l'option de compilation -ftrapv, et
l'interception du signal SIGABRT
Pour d'autres cas d'erreur, comme la division par zéro, l'interception par signal() de SIGFPE peut aussi être utile ...Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45 /** err1.c **/ #include <stdio.h> #include <stdlib.h> #include <limits.h> #include <signal.h> #include <stdint.h> void h1(int signal) { printf("Interception signal SIGABRT (%d)\n", SIGABRT); exit(1); } int main(){ int a = INT32_MAX ; signal(SIGABRT,h1); printf("Taille int = %d\n\t a= %d\n\t a+1 = ...",sizeof(int), a); printf("%d\n", a+1); return(0); } /********* Compilation et exécution (Cygwin - gcc version 11.4.0) $ gcc -ftrapv err1.c -o err1 $ ./err1 Taille int = 4 a= 2147483647 a+1 = ...Interception signal SIGABRT (6) Compilation sans l'option -ftrapv $ gcc err1.c -o err1 $ ./err1 Taille int = 4 a= 2147483647 a+1 = ...-2147483648 */
Bien que la division par zéro est de toute façon détectée et provoque l'interruption du programme,
le point où elle se produit, peut être plus difficile (ou pas ;-) à localiser sans l'interception du signal.
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41 /** err2.c **/ #include <stdio.h> #include <stdlib.h> #include <limits.h> #include <signal.h> #include <stdint.h> void h2(int signal) { printf("Interception signal SIGFPE (%d)\n", SIGFPE); exit(2); } int main(){ int a = INT32_MAX ; int b = 0 ; signal(SIGFPE,h2); printf("a / 0 = ..."); printf("%d\n",a/b); return(0) ; } /********* Compilation et exécution (Cygwin - gcc version 11.4.0) $ gcc err2.c -o err2 $ ./err2 a / 0 = ...Interception signal SIGFPE (8) $ gcc err2.c -o err2 $ ./err2 Floating point exception */
Bonjour,
Pareil, un prof citait l'exemple de bugs mémorables dont celui où le résultat de calcul d'un sinus devenait brutalement négatif au passage de l'équateur (la latitude changeant de signe), et le prof précisait que l'avion en pilotage automatique se mettait à voler sur le dos... :lol:
... De même je n'ai jamais su si c'était avéré ou une légende
Bonjour Chrtophe,
En fait, ce sont certaines instructions qui ne font pas un appel explicite aux flags.
Mais ceux-ci existent bien dans le registre CPSR (Current Program Status Register) : N pour < 0, Z pour == 0, C pour carry, V pous oVerflow, Q pous overflow avec saturation, GE pour >=. Il peut être lu et écrit via l'instruction MSR.
Salut
Il me semble que le flag overflow se base sur l'analyse des signes des deux opérandes et du résultat.
Pour l'addition : si les deux opérandes en lice ont même signe et si le résultat porte un signe différent alors il y débordement.
Si les opérandes ont des signes différents il n'y a jamais de débordement.
Pour la soustraction ... ?
Bonjour henderson,
C'est bien possible mais l'overflow existe aussi pour les non signés. Par ailleurs, il a manifestement un lien avec le carry/borrow. L'avantage d'avoir est flag dédié est de ne pas avoir d'opération de combinaison de divers flags à faire pour décider d'un branchement ou d'une opération conditionnel. Et ce n'est pas juste de la paresse mais aussi de l'efficacité car les flags sont produits en même temps que l'opération qui les entrainent.
Salut
Bonjour,
Les spécifications MIPS-V précisent (entre autres choses, ici je n'ai pris que l'exemple des entiers) :
Pièce jointe 654512
qui s'utilise ainsi :
Pièce jointe 654513
Pièce jointe 654514
Mon humble interprétation est que s'il n'y a plus de registre d'état en tant que tel, les drapeaux existent toujours, la spécification ne précise pas comment ils sont implantés, qu'en pensez-vous ?
On peut supposer que les compilateurs intègreront les options qui permettront d'exploiter ces instructions, possiblement en lien avec les buitl-in functions citées dans la présente discussion par uanonym le 2/3 à 10h38
Exemple d'utilisation Canergie Mellon University
Code:
1
2
3
4
5
6
7
8 void f(signed int si_a, signed int si_b) { signed int sum; if (__builtin_sadd_overflow(si_a, si_b, &sum)) { /* Handle error */ } /* ... */ }
Cordialement
Bonjour,
Wikipedia :
"Another alternative to the status register is for processor instructions to deposit status information in a general-purpose register when the program requests it. MIPS, AMD 29000, DEC Alpha, and RISC-V are examples of architectures that provide comparison instructions that store the comparison result in a general-purpose register, as a single bit or a numeric value of 0 or 1. Conditional branches act based on the value in the general-purpose register."
Hormis pour Arm qui a un registre dédié, les CPU ci-dessus utilisent un registre général pour stocker les flags en attente éventuel d'usage. En résumé, sauf erreur, les flags existent toujours et sont stockés dans un registre, dédié ou non.
Si on comprend que les flags historiquement caractérisaient l'état d'un système qui traitait les opérations sagement dans l'ordre sans aucun parallélisme (parallélisme au sein même du core), ce n'est plus le cas aujourd'hui. J'aurais même tendance à considérer que les flags caractérisent le résultat de chaque opération, ils pourraient donc être dans un registre secondaire adjoint à chaque registre général. Une opération entraînerait alors la modification du registre cible et son registre flag associé. L'avantage serait de casser le lien de dépendance directe entre opérations, tests et branchements.
On peut toujours rêver un peu.
Salutations
Comme je l'ai dit, l'overflow se calcule sur les signes des 2 opérandes et le résultat donc si on ne peut pas récuper le flag V :
Ce que j'ai testé :Code:
1
2
3
4
5
6
7
8
9
10
11
12
13 #define smsk 0x80000000 bool Overflow(int A, int B, int R) { if((A & smsk) == (B & smsk)) { // si de même signe alors le résultat doit avoir le même signe // sinon il y a overflow return ((A & smsk) != (R & smsk)); } // signes différents donc jamais d'overflow return false; }
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 int a, b, res; // négatif + positif a = 0xFFFFFFFF; b = 1; res = a+b; if(Overflow(a,b,res) { // ... } // positif + positif a = 0x7FFFFFFF; b = 1; ... ... // negatif + negatif ... a = 0xAF4500FF; b = 0x9000F000; ... ... // positif + negatif... a = 264; b = -3000;
Bêtement parce qu'il n'y ni carry ni overflow dans ce que tu montres !Citation:
Cela suppose des entiers signés, les unsigned peuvent avoir des overflow comme uint32_t(1) - uint32_t(0xFFFFFFFF) = 2 sans faire apparaître d'équivalent de signe posant problème.
Une soustraction est effectuée en additionnant un complément à deux !
Le résultat 2 donné par l'expression 1 - (-1) sera traitée comme 1 + 1 !
Bonjour henderson,
Faux.
Un entier non signé correspondant à 0xFFFFFFFF est 4294967295 or 1 - 4294967295 ne peut être un entier non signé. C'est la définition même d'un overflow : le résultat exact est -4294967294 et une valeur négative dépasse la capacité d'un non signé.
Comme c'est la même opération SUB qui est utilisée pour les signés et les non signés, elle ne se différencie que par l'usage des flags. CF détecte les overflows sur non signés et OF les overflows sur signés (signe exclus contrairement à CF). La doc Intel ou AMD explique cela.
Le résultat 2 n'est satisfaisant que pour des valeurs signées. Pour un entier non signé, 2 correspond à 1 - 4294967295 modulo 232 soit ...FF 00000002 & ..00 FFFFFFFF = 2. L'overflow CF permet ici de détecter le dépassement d'un uint32_t mais n'a pas d'intérêt pour un entier signé.
Quand on fait un calcul sur des entiers multi mots, seul le mot de rang le plus élevé est signé, les autres ne le sont pas. C'est pourquoi on utilise CF pour cascader les retenues.
Salut
Tu as montré une soustraction 1 - (-1) qui donne 2 !
La soustraction se résume à ajouter un complément à deux !
0xFFFFFFFF vaut -1 son complément à deux vaut 1 donc 1 + 1 = 2