web-dev-qa-db-de.com

Assemblersprache - Wie man Modulo macht?

Gibt es so etwas wie einen Modulo-Operator oder eine Anweisung in der x86-Assembly?

34
enne87

Wenn Ihr Modul/Divisor eine bekannte Konstante ist und Sie Wert auf Leistung legen, lesen Sie this und this . Eine multiplikative Inverse ist sogar für schleifeninvariante Werte möglich, die erst zur Laufzeit bekannt sind, z. siehe https://libdivide.com/ (Ohne JIT code-gen ist dies jedoch weniger effizient als nur die für eine Konstante erforderlichen Schritte hart zu codieren.)

Benutze div niemals für bekannte Potenzen von 2: es ist viel langsamer als and für Rest oder Rechtsverschiebung für Teilung. Schauen Sie sich die C-Compiler-Ausgabe an, um Beispiele für vorzeichenlose oder vorzeichenbehaftete Division durch Potenzen von 2 zu finden, z. im Godbolt-Compiler-Explorer . Wenn Sie wissen, dass eine Runtime-Eingabe eine Potenz von 2 ist, verwenden Sie lea eax, [esi-1]; and eax, edi Oder so ähnlich, um x & (y-1) auszuführen. Modulo 256 ist noch effizienter: movzx eax, cl Hat bei den neuesten Intel-CPUs keine Latenz (mov-elimination), solange die beiden Register getrennt sind.


Im einfachen/allgemeinen Fall: unbekannter Wert zur Laufzeit

Die DIV-Anweisung (und sein Gegenstück IDIV für vorzeichenbehaftete Zahlen) gibt sowohl den Quotienten als auch den Rest an. Für unsignierte sind Rest und Modul dasselbe. Für vorzeichenbehaftetes idiv gibt es den Rest (nicht den Modul) , was negativ sein kann:
z.B. -5 / 2 = -2 rem -1. Die Semantik der x86-Division entspricht genau dem Operator % von C99.

DIV r32 Dividiert eine 64-Bit-Zahl in EDX:EAX Durch einen 32-Bit-Operanden (in einem beliebigen Register oder Speicher) und speichert den Quotienten in EAX und den Rest in EDX. Es tritt ein Fehler beim Überlaufen des Quotienten auf.

Vorzeichenloses 32-Bit-Beispiel (funktioniert in jedem Modus)

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

In der 16-Bit-Assembly können Sie mit div bx Einen 32-Bit-Operanden in EDX:EAX Durch EBX teilen. Weitere Informationen finden Sie unter Intels Architectures Software Developer's Manuals .

Verwenden Sie normalerweise immer xor edx,edx Vor dem vorzeichenlosen div, um EAX in EDX: EAX auf Null zu erweitern. So machen Sie eine "normale" 32-Bit/32-Bit-Division => 32-Bit.

Verwenden Sie für eine Division mit Vorzeichen cdq vor idiv, um sign - EAX zu erweitern in EDX: EAX. Siehe auch Warum sollte EDX 0 sein, bevor der DIV-Befehl verwendet wird? . Verwenden Sie für andere Operandengrößen cbw (AL-> AX), cwd (AX-> DX: AX), cdq (EAX-> EDX: EAX), oder cqo (RAX-> RDX: RAX), um die obere Hälfte entsprechend dem Vorzeichenbit der unteren Hälfte auf 0 oder -1 zu setzen.

div/idiv sind in den Operandengrößen 8, 16, 32 und (im 64-Bit-Modus) 64-Bit verfügbar. Die 64-Bit-Operandengröße ist auf aktuellen Intel-CPUs viel langsamer als die 32-Bit-Operandengröße oder weniger, AMD-CPUs kümmern sich jedoch nur um die tatsächliche Größe der Zahlen, unabhängig von der Operandengröße.

Beachten Sie, dass die 8-Bit-Operandengröße eine Besonderheit ist: Die impliziten Ein-/Ausgänge befinden sich in AH: AL (auch bekannt als AX) und nicht in DL: AL. Siehe 8086-Assembly unter DOSBox: Fehler mit idiv-Anweisung? für ein Beispiel.

Beispiel für eine 64-Bit-Teilung mit Vorzeichen (erfordert 64-Bit-Modus)

   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

Einschränkungen/häufige Fehler

div dword 10 Kann nicht in Maschinencode codiert werden (daher meldet Ihr Assembler einen Fehler bei ungültigen Operanden).

Im Gegensatz zu mul/imul (wo Sie normalerweise schneller 2-Operanden imul r32, r/m32 Oder 3-Operanden imul r32, r/m32, imm8/32 Verwenden sollten, verschwenden Sie keine Zeit damit, a zu schreiben High-Half-Ergebnis), es gibt keinen neueren Opcode für die Division durch eine Sofortdivision oder 32-Bit/32-Bit => 32-Bit-Division oder Rest ohne die High-Half-Dividendeneingabe.

Die Aufteilung ist so langsam und (hoffentlich) selten, dass sie sich nicht die Mühe gemacht haben, einen Weg zu finden, um EAX und EDX zu umgehen, oder einen direkten Weg zu wählen.


div und idiv werden fehlerhaft, wenn der Quotient nicht in ein Register passt (AL/AX/EAX/RAX, dieselbe Breite wie der Dividend). Dies schließt die Division durch Null ein, wird aber auch mit einem EDX ungleich Null und einem kleineren Divisor geschehen. Dies ist der Grund, warum C-Compiler nur eine Null-Erweiterung oder eine Vorzeichenerweiterung durchführen, anstatt einen 32-Bit-Wert in DX: AX aufzuteilen.

Und auch, warum INT_MIN / -1 Nicht definiertes Verhalten ist: Es überläuft den vorzeichenbehafteten Quotienten auf 2er Komplementsystemen wie x86. Siehe Warum führt die Ganzzahldivision durch -1 (negative Eins) zu FPE? für ein Beispiel von x86 vs. ARM. x86 idiv ist in diesem Fall tatsächlich fehlerhaft.

Die x86-Ausnahme ist #DE - Divide Exception. Auf Unix/Linux-Systemen liefert der Kernel ein SIGFPE-arithmetisches Ausnahmesignal an Prozesse, die eine #DE-Ausnahme verursachen. ( Auf welchen Plattformen löst eine Ganzzahl-Division durch Null eine Gleitkomma-Ausnahme aus? )

Für div ist die Verwendung einer Dividende mit high_half < divisor Sicher. z.B. 0x11:23 / 0x12 Ist kleiner als 0xff Und passt daher in einen 8-Bit-Quotienten.

Die Division einer großen Zahl durch eine kleine Zahl mit erweiterter Genauigkeit kann implementiert werden, indem der Rest von einem Block als die High-Half-Dividende (EDX) für den nächsten Block verwendet wird. Dies ist wahrscheinlich der Grund, warum sie den Rest = EDX-Quotienten = EAX gewählt haben und nicht umgekehrt.

66
user786653

Wenn Sie modulo mit einer Zweierpotenz berechnen, ist die bitweise UND-Verknüpfung einfacher und im Allgemeinen schneller als die Division. Wenn b eine Zweierpotenz ist, a % b == a & (b - 1).

Nehmen wir zum Beispiel einen Wert in register EAX, modulo 64.
Der einfachste Weg wäre AND EAX, 63, weil 63 in binär 111111 ist.

Die maskierten, höheren Ziffern interessieren uns nicht. Versuch es!

Anstatt MUL oder DIV mit Zweierpotenzen zu verwenden, ist die Bitverschiebung der richtige Weg. Vorsicht vor vorzeichenbehafteten ganzen Zahlen!

26
Andreiasw