Я пытаюсь разделить два числа 50 и 5. Это мой код:
function Divide(Num1, Num2: Integer): Integer;
asm
MOV EAX, Num1
CDQ
MOV ECX, Num2
IDIV ECX
MOV @RESULT, ECX
end;
Это дает мне исключение DivisionByZeroException
в Delphi. Может кто-нибудь сказать мне, что я делаю не так?
2 ответа
Это инструкция CDQ
. Из онлайн-справки:
Преобразует знаковый DWORD в EAX в четверное слово со знаком в EDX: EAX путем расширения старшего бита EAX по всему EDX.
Проблема в том, что Num2
, являющийся вторым параметром, хранится в EDX, и, поскольку вы запускаете CDQ
перед загрузкой EDX в ECX, в ECX получается 0. Перепишите его так, и ваша рутина работает, как ожидалось:
function Divide(Num1, Num2: integer): integer;
asm
MOV EAX, Num1
MOV ECX, Num2
CDQ
IDIV ECX
MOV @Result, EAX
end;
Ответ Мэйсона точен и ясно объясняет ошибку из-за расширения знака CDQ, перезаписывающего входной параметр в EDX. Мне не нужно больше говорить, Мейсон все понял. И обратите внимание на поправку, согласно которой IDIV возвращает частное в EAX, а не в ECX.
Я хотел бы попытаться дать несколько более общих советов по написанию asm. Я считаю, что ваша основная проблема здесь заключается в использовании имен параметров в вашем asm, а не имен регистров.
Поскольку вы используете соглашение о вызове регистров, действительно стоит четко указывать, что параметры поступают в регистры. Если бы вы сделали это, было бы яснее, что происходит. Попытка использовать имена переменных создает иллюзию абстракции. На самом деле этой абстракции нет. Скрывая передачу параметра регистра из поля зрения, вы затрудняете обнаружение таких ошибок, и, конечно же, вы наступили на свой ввод!
Прежде всего, давайте напишем код в ответе Мейсона в терминах регистров. Включите комментарии для большей ясности. Как это:
function Divide(Num1, Num2: integer): integer;
// Input: EAX: Num1, EDX: Num2
// Output: EAX: Result
asm
MOV EAX, EAX
MOV ECX, EDX
CDQ
IDIV ECX
MOV EAX, EAX
end;
Сразу же мы получаем немедленное преимущество в том, что первая и последняя строки явно бессмысленны. Вы не могли увидеть это в своей версии из-за использования имен переменных.
Итак, мы можем написать это так:
function Divide(Num1, Num2: integer): integer;
// Input: EAX: Num1, EDX: Num2
// Output: EAX: Result
asm
MOV ECX, EDX
CDQ
IDIV ECX
end;
Конечно, не случайно, что большинство арифметических операций возвращают результат в EAX, и тот же регистр используется для значений, возвращаемых функцией.
Дело в том, что написание asm сводится к пониманию использования и повторного использования регистров. Не скрывайте это с помощью имен переменных. Держите регистр использования спереди и по центру, на виду. Как только вы начнете это делать, вам не составит труда обнаружить ошибки, как в вопросе, и вы сможете удалить ложные операции, когда значения попадут в правильные регистры.
Мой совет - никогда не использовать имена параметров или Result
в asm-коде.
Другой очень очевидный момент заключается в том, что вы повторно реализуете оператор div
. Помещая это в asm-функцию, вы неизбежно делаете код менее эффективным и менее читаемым.
Как бы то ни было, эта конкретная функция может быть написана более эффективно на языке Pascal. Рассмотрим следующую программу:
{$APPTYPE CONSOLE}
function DivideAsm(Num1, Num2: integer): integer;
// Input: EAX: Num1, EDX: Num2
// Output: EAX: Result
asm
MOV ECX, EDX
CDQ
IDIV ECX
end;
function DividePas(Num1, Num2: integer): integer;
begin
Result := Num1 div Num2;
end;
function DividePasInline(Num1, Num2: integer): integer; inline;
begin
Result := Num1 div Num2;
end;
var
i, j, k, l: Integer;
begin
i := 666;
j := 42;
l := 0;
inc(l, i div j);
inc(l, DivideAsm(i, j));
inc(l, DividePas(i, j));
inc(l, DividePasInline(i, j));
Writeln(l);
end.
Теперь DividePas
хуже, чем DivideAsm
. Первый компилируется с оптимизацией для:
0040524C 53 push ebx 0040524D 8BDA mov ebx,edx 0040524F 8BC8 mov ecx,eax 00405251 8BC1 mov eax,ecx 00405253 99 cdq 00405254 F7FB idiv ebx 00405256 5B pop ebx 00405257 C3 ret
Очевидно, что DivideAsm
выигрывает, если пропустить пролог / эпилог.
Но давайте посмотрим на основную часть кода:
SO22570866.dpr.28: i := 666; 004060D7 BE9A020000 mov esi,$0000029a SO22570866.dpr.29: j := 42; 004060DC BF2A000000 mov edi,$0000002a SO22570866.dpr.30: l := 0; 004060E1 33DB xor ebx,ebx SO22570866.dpr.31: inc(l, i div j); 004060E3 8BC6 mov eax,esi 004060E5 99 cdq 004060E6 F7FF idiv edi 004060E8 03D8 add ebx,eax SO22570866.dpr.32: inc(l, DivideAsm(i, j)); 004060EA 8BD7 mov edx,edi 004060EC 8BC6 mov eax,esi 004060EE E851F1FFFF call DivideAsm 004060F3 03D8 add ebx,eax SO22570866.dpr.33: inc(l, DividePas(i, j)); 004060F5 8BD7 mov edx,edi 004060F7 8BC6 mov eax,esi 004060F9 E84EF1FFFF call DividePas 004060FE 03D8 add ebx,eax SO22570866.dpr.34: inc(l, DividePasInline(i, j)); 00406100 8BC6 mov eax,esi 00406102 99 cdq 00406103 F7FF idiv edi 00406105 03D8 add ebx,eax
Вы можете видеть, что компилятор имеет больше свободы в использовании регистров со встроенной версией. Компилятор не привязан к соглашению о вызовах ABI. Это позволяет ему выполнять меньше операций MOV
. На самом деле взаимодействие между встроенным движком и оптимизатором очень хорошее. Вот первая версия кода, который я написал:
inc(l, DivideAsm(i, j));
inc(l, DividePas(i, j));
inc(l, i div j);
inc(l, DividePasInline(i, j));
Но оптимизатор побеждает меня по последним двум утверждениям:
SO22570866.dpr.33: inc(l, i div j); 004060F9 8BC6 mov eax,esi 004060FB 99 cdq 004060FC F7FF idiv edi 004060FE 8BC8 mov ecx,eax 00406100 03D9 add ebx,ecx SO22570866.dpr.34: inc(l, DividePasInline(i, j)); 00406102 03D9 add ebx,ecx
Оптимизатор может распознать, что регистр ECX
уже содержит результат DividePasInline
, и полностью пропускает код!
Похожие вопросы
Новые вопросы
delphi
Delphi - это язык для быстрой разработки собственных приложений для Windows, macOS, Linux, iOS и Android с использованием Object Pascal. Название относится к языку Delphi, а также к его библиотекам, компилятору и IDE, которые используются для помощи в редактировании и отладке проектов Delphi.
div
? Это в образовательных целях?