Existe-t-il quelque chose comme un opérateur ou une instruction modulo dans un assemblage x86?
Si votre module/diviseur est une constante connue et que vous vous souciez des performances, consultez this et this . Un inverse multiplicatif est même possible pour les valeurs invariantes en boucle qui ne sont pas connues avant l'exécution, par ex. voir https://libdivide.com/ (Mais sans code-gen JIT, c'est moins efficace que de coder en dur juste les étapes nécessaires pour une constante.)
N'utilisez jamais div
pour des puissances connues de 2: c'est beaucoup plus lent que and
pour le reste, ou vers la droite pour la division. Regardez la sortie du compilateur C pour des exemples de division non signée ou signée par puissances de 2, par exemple sur l'explorateur du compilateur Godbolt . Si vous savez qu'une entrée d'exécution est une puissance de 2, utilisez lea eax, [esi-1]
; and eax, edi
Ou quelque chose comme ça pour faire x & (y-1)
. Modulo 256 est encore plus efficace: movzx eax, cl
N'a aucune latence sur les processeurs Intel récents (mov-élimination), tant que les deux registres sont séparés.
L'instruction DIV
(et son équivalent IDIV
pour les numéros signés) donne à la fois le quotient et le reste. Pour non signé, reste et module sont la même chose. Pour les idiv
signés, cela vous donne le reste (pas le module) qui peut être négatif:
par exemple. -5 / 2 = -2 rem -1
. La sémantique de division x86 correspond exactement à l'opérateur %
de C99.
DIV r32
Divise un nombre 64 bits dans EDX:EAX
Par un opérande 32 bits (dans n'importe quel registre ou mémoire) et stocke le quotient dans EAX
et le reste dans EDX
. Il fait défaut sur débordement du quotient.
Exemple 32 bits non signé (fonctionne dans n'importe quel mode)
mov eax, 1234 ; dividend low half
mov edx, 0 ; dividend high half = 0. prefer xor edx,edx
mov ebx, 10 ; divisor can be any register or memory
div ebx ; Divides 1234 by 10.
; EDX = 4 = 1234 % 10 quotient
; EAX = 123 = 1234 / 10 remainder
Dans l'assemblage 16 bits, vous pouvez faire div bx
Pour diviser un opérande 32 bits en EDX:EAX
Par EBX
. Voir Intels Architectures Software Developer’s Manuals pour plus d’informations.
Normalement, utilisez toujours xor edx,edx
Avant de ne pas signer div
pour étendre zéro EAX dans EDX: EAX. Voici comment vous effectuez la division "normale" 32 bits/32 bits => 32 bits.
Pour la division signée, utilisez cdq
avant idiv
à signe - étendez EAX dans EDX: EAX. Voir aussi Pourquoi EDX devrait être 0 avant d'utiliser l'instruction DIV? . Pour les autres tailles d'opérande, utilisez cbw
(AL-> AX), cwd
(AX-> DX: AX), cdq
(EAX-> EDX: EAX), ou cqo
(RAX-> RDX: RAX) pour régler la moitié supérieure sur 0
ou -1
en fonction du bit de signe de la moitié inférieure.
div
/idiv
sont disponibles dans des tailles d'opérande de 8, 16, 32 et (en mode 64 bits) 64 bits. La taille d'opérande 64 bits est beaucoup plus lente que 32 bits ou moins sur les processeurs Intel actuels, mais les processeurs AMD ne se soucient que de l'ampleur réelle des nombres, quelle que soit la taille de l'opérande.
Notez que la taille de l'opérande 8 bits est spéciale: les entrées/sorties implicites sont en AH: AL (aka AX), pas DL: AL. Voir 8086 Assembly on DOSBox: Bug with idiv instruction? pour un exemple.
Exemple de division 64 bits signé (nécessite le mode 64 bits)
mov rax, 0x8000000000000000 ; INT64_MIN = -9223372036854775808
mov ecx, 10 ; implicit zero-extension is fine for positive numbers
cqo ; sign-extend into RDX, in this case = -1 = 0xFF...FF
idiv rcx
; quotient = RAX = -922337203685477580 = 0xf333333333333334
; remainder = RDX = -8 = 0xfffffffffffffff8
div dword 10
N'est pas encodable en code machine (donc votre assembleur signalera une erreur sur les opérandes invalides).
Contrairement à mul
/imul
(où vous devriez normalement utiliser plus rapidement 2 opérandes imul r32, r/m32
Ou 3 opérandes imul r32, r/m32, imm8/32
À la place qui ne perdent pas de temps à écrire un résultat demi-haut), il n'y a pas d'opcode plus récent pour la division par une division immédiate, ou 32 bits/32 bits => 32 bits ou le reste sans l'entrée de dividende demi-haut.
La division est si lente et (espérons-le) rare qu'ils n'ont pas pris la peine d'ajouter un moyen pour vous permettre d'éviter EAX et EDX, ou d'utiliser directement un immédiat.
div et idiv seront défectueux si le quotient ne rentre pas dans un registre (AL/AX/EAX/RAX, la même largeur que le dividende). Cela inclut la division par zéro, mais cela se produira également avec un EDX non nul et un diviseur plus petit. C'est pourquoi les compilateurs C étendent simplement zéro ou étendent le signe au lieu de diviser une valeur 32 bits en DX: AX.
Et aussi pourquoi INT_MIN / -1
Est un comportement C non défini: il déborde le quotient signé sur les systèmes complémentaires de 2 comme x86. Voir Pourquoi la division entière par -1 (un négatif) entraîne-t-elle FPE? pour un exemple de x86 par rapport à ARM. x86 idiv
fait en effet défaut dans ce cas.
L'exception x86 est #DE
- exception de division. Sur les systèmes Unix/Linux, le noyau délivre un signal d'exception arithmétique SIGFPE aux processus qui provoquent une exception #DE. ( Sur quelles plateformes l'entier divise par zéro déclenche-t-il une exception à virgule flottante? )
Pour div
, l'utilisation d'un dividende avec high_half < divisor
Est sûre. par exemple. 0x11:23 / 0x12
Est inférieur à 0xff
, Il tient donc dans un quotient de 8 bits.
La division de précision étendue d'un grand nombre par un petit nombre peut être implémentée en utilisant le reste d'un bloc comme le demi-dividende élevé (EDX) pour le bloc suivant. C'est probablement pourquoi ils ont choisi reste = EDX quotient = EAX au lieu de l'inverse.
Si vous calculez modulo une puissance de deux, l'utilisation ET binaire est plus simple et généralement plus rapide que la division. Si b
est une puissance de deux, a % b == a & (b - 1)
.
Par exemple, prenons une valeur dans le registre EAX, modulo 64.
Le moyen le plus simple serait AND EAX, 63
, car 63 est 111111 en binaire.
Les chiffres masqués et supérieurs ne nous intéressent pas. Essaye le!
De manière analogue, au lieu d'utiliser MUL ou DIV avec des puissances de deux, le décalage binaire est la voie à suivre. Attention aux entiers signés!