Le débordement d’entier est-il indéfini dans l’assemblage en ligne x86?

Disons que j’ai le code C suivant:

int32_t foo(int32_t x) { return x + 1; } 

Ce comportement n’est pas défini lorsque x == INT_MAX . Supposons maintenant que j’ai effectué l’ajout avec l’assemblage en ligne:

 int32_t foo(int32_t x) { asm("incl %0" : "+g"(x)); return x; } 

Question: La version d’assembly en ligne invoque-t-elle toujours un comportement indéfini lorsque x == INT_MAX ? Ou le comportement non défini s’applique-t-il uniquement au code C?

Non , il n’y a pas d’UB avec ça. Les règles C ne s’appliquent pas aux instructions asm elles-mêmes. En ce qui concerne la syntaxe inline-asm englobant les instructions, il s’agit d’une extension de langage bien définie qui a un comportement défini pour les implémentations qui la prennent en charge.

Voir Est – ce que le comportement non défini s’applique au code asm? pour une version plus générique de cette question (par opposition à celle sur l’assemblage x86 et l’extension de langage GNU C inm). Les réponses se concentrent sur le côté C, avec des citations des normes C et C ++ qui décrivent le peu de choses que la norme dit sur les extensions du langage définies par l’implémentation.

Voir aussi ce fil de discussion comp.lang.c pour savoir s’il est judicieux de dire qu’il a UB “en général” car toutes les implémentations ne possèdent pas cette extension.


BTW, si vous voulez juste un enveloppement signé avec un comportement de complément à 2 défini dans GNU C, comstackz avec -fwrapv . Ne pas utiliser inline asm. (Ou utilisez un __atsortingbute__ pour activer cette option uniquement pour la fonction qui en a besoin.) wrapv n’est pas tout à fait la même chose que -fno-ssortingct-overflow , qui désactive simplement les optimisations basées sur l’hypothèse que le programme n’a pas d’UB; par exemple, le dépassement de -fwrapv calculs de constante de temps de compilation n’est sans danger qu’avec -fwrapv .


Le comportement inline-asm est défini par l’implémentation, et GNU C inline asm est défini comme une boîte noire pour le compilateur. Les entrées entrent, les sorties sortent et le compilateur ne sait pas comment. Tout ce qu’il sait, c’est ce que vous dites en utilisant les contraintes out / in / clobber.


Votre foo qui utilise inline-asm se comporte de manière identique à

 int32_t foo(int32_t x) { uint32_t u = x; return ++u; } 

sur x86, parce que x86 est une machine à complément à 2, le wraparound entier est donc bien défini. (Sauf pour les performances: la version asm annule la propagation de la constante et ne permet pas au compilateur d’optimiser x - inc(x) à -1, etc. etc. https://gcc.gnu.org/wiki/DontUseInlineAsm sauf aucun moyen de convaincre le compilateur de générer un asm optimal en peaufinant le C.)

Il ne soulève pas d’exceptions. La définition de l’indicateur OF n’a aucun impact, car GNU C inline as x pour x86 (i386 et amd64) possède un clobber "cc" implicite. Le compilateur part donc du principe que les codes de condition dans EFLAGS conservent un garbage après chaque instruction inline-asm. gcc6 a introduit une nouvelle syntaxe pour que asm produise des résultats d’indicateur (permettant de sauvegarder un SETCC dans votre asm et un TEST généré par le compilateur pour les blocs asm qui souhaitent renvoyer une condition d’indicateur).

Certaines architectures soulèvent des exceptions (interruptions) en cas de dépassement d’entier, mais x86 n’en fait pas partie ( sauf lorsqu’un quotient de division ne rentre pas dans le registre de destination ). Sur MIPS, vous utiliseriez ADDIU au lieu d’ADDI sur des entiers signés si vous voulez qu’ils puissent s’envelopper sans recouvrement. (Comme il s’agit également d’un complément ISA à 2, l’enveloppement signé est donc identique en binary à celui non signé.)


Comportement non défini (ou au moins dépendant de l’implémentation) dans x86 asm:

BSF et BSR (trouver le premier bit mis en avant ou en sens inverse) laissent leur registre de destination avec un contenu indéfini si l’entrée était à zéro. (TZCNT et LZCNT n’ont pas ce problème). Les processeurs x86 récents d’Intel définissent le comportement, qui consiste à laisser la destination non modifiée, mais les manuels x86 ne le garantissent pas. Reportez-vous à la section TZCNT dans cette réponse pour plus d’informations sur les implications, par exemple, le fait que TZCNT / LZCNT / POPCNT a une fausse dépendance sur la sortie des processeurs Intel.

Plusieurs autres instructions laissent certains indicateurs indéfinis dans certains cas. (surtout AF / PF). IMUL, par exemple, laisse ZF, PF et AF indéfinis.

Tout processeur donné a vraisemblablement un comportement cohérent, mais le fait est que d’autres peuvent se comporter différemment même s’ils sont toujours x86. Si vous êtes Microsoft, Intel concevra ses futurs processeurs pour ne pas casser votre code existant. Si votre code est si largement utilisé, vous feriez mieux de ne vous fier qu’au comportement décrit dans les manuels, et pas seulement à ce que votre processeur fait. Voir la réponse et les commentaires de Andy Glew ici . Andy était l’un des architectes de la microarchitecture P6 d’Intel.

Ces exemples ne sont pas la même chose que UB en C. Ils ressemblent davantage à ce que C appellerait “implémentation définie”, car nous ne parlons que d’une valeur non spécifiée, pas de la possibilité de démons nasaux . (Ou le plus plausible modifier d’autres registres, ou sauter quelque part).

Pour un comportement vraiment indéfini, vous devez probablement consulter les instructions privilégiées, ou du moins le code multithread. Le code à modification automatique est également potentiellement UB sur x86: il n’est pas garanti que la CPU “remarque” enregistre sur des adresses sur le point d’être exécutées jusqu’après une instruction de saut. C’est le sujet de la question liée ci-dessus (et la réponse est: les vraies implémentations de x86 vont au-delà des exigences du manuel ISA x86, pour prendre en charge un code qui en dépend et parce que l’espionnage est toujours meilleur pour les performances élevées. que la chasse sur les sauts.)

Un comportement indéfini en langage assembleur est assez rare, en particulier si vous ne comptez pas les cas dans lesquels une valeur spécifique n’est pas spécifiée, mais que la scope du “dommage” est prévisible et limitée.

Eh bien, la norme C ne définit pas ce que l’assembleur en ligne définit, donc tout assembleur en ligne a un comportement indéfini conformément à la norme C.

Vous utilisez un langage légèrement différent, “C avec assembleur en ligne 32 bits x86”. Vous avez généré une instruction assembleur valide. Le comportement est vraisemblablement défini par les manuels de référence d’Intel. Et là, le comportement d’une addition d’entiers ajoutant 1 à INT_MAX est bien défini. Il est défini de manière à ne pas nuire à l’exécution de votre programme C.

L’assembleur en ligne qui a essayé de lire une valeur via un pointeur null serait également bien défini au niveau de l’assembleur, mais son comportement interférerait avec l’exécution de votre programme (c’est-à-dire son crash).