Использование ассемблера в Дельфи


         

Возврат информации через регистры процессора


В большинстве случаев (зависит от типа результата), функция возвращает результат через  регистры процессора. Напомним, что в отличие от передачи параметров в функцию, где в большинстве соглашений используют стек, для возврата результата, все соглашения используют регистры для допустимых типов!

Таблица 3 содержит обзор о вариантах возврата результатов. В большинстве случаев, результат возвращается через регистр EAX или FP(0). Особый случай, когда в результате возвращается длинная строка или другой тип, возвращаемый через указатель. В случае длинных строк, динамических массивов, больших множеств, вариантов и больших записей, переданных через параметр с директивой var, используется 32-битный указатель на результат. Так, где же хранится действительное содержимое результата (например, длинных строк)? Ответ на это в том, что вы должны выделить место в куче, заполнить его данными, и вернуть указатель на эту область памяти через переменную Result. Заметим, что, для множеств, записей и массивов, которые могут разместиться в регистре, переменная Result возвращает их через регистр. Только для длинных строк, вариантов и множеств, записей и массивов, которые занимают свыше 32 бит, переменная Result возвращает указатель на дополнительный указатель, размещенный функцией, аналогично директиве var параметра (мы рассмотрим директиву параметра var в главе 1.9).

Не беспокойтесь, если что-то сейчас не понятно, позже мы рассмотрим эти типы подробнее.

Теперь поясним это на примере. Функция PlusMinusLine возвращает длинную строку, состоящую из последовательности плюсов и минусов, для формирования строки. Например, когда вы напишите так S:=PlusMinusLine(9), то S должна получить значение: "-+-+-+-+-".

Декларация функции следующая:

function PlusMinusLine(L: Integer): AnsiString; register;

Функция принимает один параметр: длину строки символов (L). Поскольку мы используем соглашение по умолчанию, то параметр передается через регистр EAX. Функция должна вернуть длинную строку, Что в действительности означает указатель на область памяти, содержащей нашу строку. Вы можете использовать переменную Result для обращения к этой области, но поскольку ее поведение аналогично var, то в этом случае @Result эквивалентно регистру EDX (второй параметр отдельной функции передается через регистр EDX, при использования соглашения register)! Подробности мы рассмотрим в главе 1.9. EDX не содержит самого указателя, но указатель на область памяти для этого указателя! Тем не менее, пока еще не распределена память для нашей длинной строки. Будем использовать функцию NewAnsiString из модуля system для размещения памяти в куче и установке длины строки. Функция NewAnsiString


устанавливает длину новой строки, которая передается через регистр EAX и возвращает адрес этой строки в том же регистре. Если же мы не вызовем функцию NewAnsiString (или другую функцию или процедуру, которая выделит память в куче для нашей длинной строки), то переменная Result не будет содержать действительного указателя и мы можем получить ошибку доступа (access violation) если попытаемся использовать его.

function PlusMinusLine(L: Integer): AnsiString; register;

asm

push EDI

push ESI

push EBX

mov ESI,EDX {Указатель памяти на Result}

mov EBX,EAX {EBX хранит длину параметра}

call System.@NewAnsiString

mov EDI,EAX {EDI используется для заполнения строки}

mov [ESI],EDI

mov ECX,EBX

shr ECX,2 {обрабатываем по 4 байта за раз}

test ECX,ECX

jz @@remain

mov EAX,'+-+-'

@@loop:

mov [EDI],EAX

add EDI,4

dec ECX

jnz @@loop

@@remain: {заполняем оставшие байты, если length/4 не ноль}

mov ECX,EBX

and ECX,3

jz @@ending

mov EAX,'+-+-'

@@loop2:

mov BYTE PTR [EDI],al

shr EAX,8

inc EDI

dec ECX

jnz @@loop2

@@ending:

mov EAX,ESI {для совместимости: возврат указателя через EAX}

pop EBX

pop ESI

pop EDI

end;

Для облегчения понимания, данного примера пришлось пожертвовать некоторой эффективностью. Для ускорения за раз одновременно обрабатывается по 4 байта для заполнения строки. Тем не менее, можно сделать еще быстрее, если использовать указатель в EDI на конец строки и использовать отрицательный счетчик в ECX, постепенно увеличивая его до нуля и используя его как индекс ([EDI+ECX*4]), что также сделало бы не нужным увеличение регистра EDI после каждой итерации. Это прекрасный повод для читателя переписать эту функцию данным образом и сравнить результаты выполнения. Также, может быть, вы захотите уменьшить количество циклов для еще большей эффективности. Например, обрабатывать по 8 байт за каждую итерацию, уменьшив этим количество переходов.

Как видим, возврат информации через регистры не всегда самый простой путь, особенно для структурных переменных типа длинных строк.



Содержание раздела