За время программирования на Ассемблере у меня накопилось несколько полезных решений. Выделять под каждое них отдельную статью не хочется, а держать под рукой пригодится. Поэтому все сложу сюда, по мере надобности буду пополнять.
Начну с функции очень быстрого копирования одного участка памяти в другой. Фишка в том, что основная часть строки копируется по 4 байта DWORD'ами, а остаток дописывается побайтно. Это дает нехилый выигрыш в скорости.
;-----------------------------------------------------
; Функция быстрого копирования участка памяти
;-----------------------------------------------------
; lpDst - указатель на приемник
; lpSrc - указатель на источник
; dSize - размер копируемого блока
;-----------------------------------------------------
proc _memcopy lpDst:DWORD, lpSrc:DWORD, dSize:DWORD
pusha
; Установить указатели на источник и приемник
cld
mov edi,[lpDst]
mov esi,[lpSrc]
mov ecx,[dSize]
push ecx
; Разделить на 4 и получить длину в DWORD
shr ecx,2
; Скопировать основную часть строки DWORD'ами
rep movsd
pop ecx
; Получить остаток от деления на 4
and ecx,3
; Скопировать остаток строки байтами
rep movsb
popa
ret
endp
Функция копирует любые данные, в том числе и нулевые байты, поэтому для нее обязательно указывать размер копируемого блока.
Еще один вариант копирования. Он более красивый по реализации, но менее эффективный по производительности за счет использования переходов и, соответственно, невозможности использования конвейера команд процессора.
;-----------------------------------------------------
; Функция быстрого копирования участка памяти
;-----------------------------------------------------
; lpDst - указатель на приемник
; lpSrc - указатель на источник
; dSize - размер копируемого блока
;-----------------------------------------------------
proc _memcopy lpDst:DWORD, lpSrc:DWORD, dSize:DWORD
pusha
; Установить указатели на источник и приемник
cld
mov edi,[lpDst]
mov esi,[lpSrc]
mov ecx,[dSize]
; Если есть остаток от деления на 2
shr ecx,1
jnc @f
; Скопировать один байт
movsb
@@:
; Если есть остаток от деления на 4
shr ecx,1
jnc @f
; Скопировать слово
movsw
@@:
; Остаток строки скопировать двойными словами
rep movsd
popa
ret
endp
Следующие несколько функций написаны для замены стандартных WinAPI-функций для работы со строками. Во-первых, они оптимизированы, поэтому выполняются быстрее штатных аналогов, а во-вторых нет нужды прописывать их в секцию импорта. Например, если ваша динамическая библиотека в своей работе использует лишь несколько функций работы со строками, то при замене их на приведенные здесь аналоги, секция импорта вообще будет не нужна. Функции полностью самодостаточны, не требуют никаких дополнительных данных. Но обратите внимание, что некоторые из них взаимосвязаны, так что если нужна только какая-нибудь одна, то в нее можно перенести код из вспомогательных функций для сокращения места.
Аналог функции lstrlen - получение длины строки. На входе адрес строки в формате ASCIIZ, на выходе EAX - длина строки.
;-----------------------------------------------------
; Функция получения длины строки
;-----------------------------------------------------
; lpStr - указатель на строку ASCIIZ
; На выходе: EAX - длина строки без учета завершающего
; нулевого байта
;-----------------------------------------------------
proc _lstrlen lpStr:DWORD
push edi ecx
cld
mov edi,[lpStr]
xor ecx,ecx
dec ecx
xor eax,eax
repne scasb
not ecx
dec ecx
mov eax,ecx
pop ecx edi
ret
endp
Еще одна функция получения длины строки. По размеру получается немного больше, но по скорости работы почти в два раза превосходит как стандартную функцию, так и предыдущую. Выбирайте сами: размер получаемого кода или производительность.
;-----------------------------------------------------
; Функция получения длины строки (Fast)
;-----------------------------------------------------
; lpStr - указатель на строку ASCIIZ
; На выходе: EAX - длина строки без учета завершающего
; нулевого байта
;-----------------------------------------------------
proc _lstrlen lpStr:DWORD
mov eax, [lpStr]
sub eax, 4
@@:
add eax, 4
cmp byte [eax], 0
je .szlen_lb1
cmp byte [eax+1], 0
je .szlen_lb2
cmp byte [eax+2], 0
je .szlen_lb3
cmp byte [eax+3], 0
jne @b
sub eax, [lpStr]
add eax, 3
ret
.szlen_lb3:
sub eax, [lpStr]
add eax, 2
ret
.szlen_lb2:
sub eax, [lpStr]
add eax, 1
ret
.szlen_lb1:
sub eax, [lpStr]
ret
endp
Аналог функции
lstrcpy - копирование строки в формате ASCIIZ. На входе адреса приемника и источника. Функция ничего не возвращает. Используются описанные выше функции _lstrlen и_memcopy.
;-----------------------------------------------------
; Функция быстрого копирования строки
; используются функции _lstrlen, _memcopy
;-----------------------------------------------------
; lpDst - указатель на приемник
; lpSrc - указатель на строку ASCIIZ
;-----------------------------------------------------
proc _lstrcpy lpDst:DWORD, lpSrc:DWORD
pusha
stdcall _lstrlen,[lpSrc]
inc eax
stdcall _memcopy,[lpDst],[lpSrc],eax
popa
ret
endp
Аналог функции
lstrcat - конкатенация (слияние) двух строк в формате ASCIIZ. Параметры: адрес основной строки и адрес второй строки, которая будет дописана к основной. Функция ничего не возвращает. Используются описанные выше функции _lstrlen и _lstrcpy.
;-----------------------------------------------------
; Функция быстрого слияния двух строк
; используются функции _lstrlen, _lstrcpy
;-----------------------------------------------------
; lpDst - указатель на исходную строку ASCIIZ
; lpSrc - указатель на добавляемую строку ASCIIZ
;-----------------------------------------------------
proc _lstrcat lpDst:DWORD, lpSrc:DWORD
pusha
stdcall _lstrlen,[lpDst]
add eax,[lpDst]
stdcall _lstrcpy,eax,[lpSrc]
popa
ret
endp
Аналог функции
lstrcmp - сравнение двух строк. На входе адреса двух строк в формате ASCIIZ, на выходе EAX=0 - строки равны, EAX=1 - строки различаются по длине и/или содержимому. Используется описанная выше функция _lstrlen.
;-----------------------------------------------------
; Функция быстрого сравнения двух строк
; используется функция _lstrlen
;-----------------------------------------------------
; lpStr1 - указатель на первую строку ASCIIZ
; lpStr2 - указатель на вторую строку ASCIIZ
; На выходе: EAX=0 - строки совпадают
; EAX=1 - строки различаются
;-----------------------------------------------------
proc _lstrcmp lpStr1:DWORD, lpStr2:DWORD
push ecx esi edi
mov esi,[lpStr1]
mov edi,[lpStr2]
stdcall _lstrlen,esi
mov ecx,eax
stdcall _lstrlen,edi
cmp ecx,eax
; Длина строк не совпадает
jnz @f
cld
repe cmpsb
@@:
setnz al
movsx eax,al
pop edi esi ecx
ret
endp
Еще одна быстрая функция сравнения двух строк. Входные и выходные параметры аналогичны предыдущей функции, никакие дополнительные функции не используются.
;-----------------------------------------------------
; Функция быстрого сравнения двух строк
;-----------------------------------------------------
; lpStr1 - указатель на первую строку ASCIIZ
; lpStr2 - указатель на вторую строку ASCIIZ
; На выходе: EAX=0 - строки совпадают
; EAX=1 - строки различаются
;-----------------------------------------------------
proc _lstrcmp lpStr1:DWORD,lpStr2:DWORD
push esi edi
mov esi,[lpStr1] ; Указатели на строки
mov edi,[lpStr2]
xor eax,eax ; Предположим, что строки равны
@@:
lodsb ; Сравнить следующие символы
mov ah,[edi]
inc edi
or al,al ; Первая строка закончилась?
jz @f ; Да
cmp al,ah ; Символы совпадают?
je @b ; Да, проверить следующий символ
@@:
or ah,ah ; Вторая строка закончилась?
je @f ; Да, строки равны
xor eax,eax ; Строки не совпадают
inc eax
@@:
pop edi esi
ret
endp
Функция быстрого регистронезависимого (только для букв английского алфавита) сравнения строк, аналог функции API
lstrcmpi. На входе адреса двух строк в формате ASCIIZ, на выходе EAX=0 - строки равны, EAX=1 - строки различаются по длине и/или содержимому. Никакие дополнительные функции не используются.
;-----------------------------------------------------
; Функция быстрого регистронезависимого сравнения
; двух строк
;-----------------------------------------------------
; lpStr1 - указатель на первую строку ASCIIZ
; lpStr2 - указатель на вторую строку ASCIIZ
; На выходе: EAX=0 - строки совпадают
; EAX=1 - строки различаются
;-----------------------------------------------------
proc _lstrcmpi lpStr1:DWORD,lpStr2:DWORD
push esi edi
mov esi,[lpStr1] ; Указатели на строки
mov edi,[lpStr2]
xor eax,eax ; Предположим, что строки равны
.loc_compare:
lodsb ; Сравнить следующие символы
mov ah,[edi]
inc edi
cmp al,ah ; Символы совпадают?
jne @f ; Нет
or al,al ; Строки закончились?
jz .loc_ret ; Да, строки совпадают
jmp .loc_compare ; Проверить следующий символ
@@:
and eax,0DFDFh ; Оба символа в верхний регистр
cmp al,'A'
jb .loc_not_equal
cmp al,'Z'
ja .loc_not_equal
cmp al,ah ; Разница была только в регистре?
je .loc_compare ; Да, следующий символ
.loc_not_equal:
xor eax,eax ; Строки не совпадают
inc eax
.loc_ret:
pop edi esi
ret
endp
Функция быстрого заполнения фрагмента памяти указанными значениями (байтами). Может применяться для очистки памяти.
;-----------------------------------------------------
; Функция быстрого заполнения блока памяти значениями
;-----------------------------------------------------
; lpDst - указатель на приемник
; dVal - заполнитель (берется младший байт)
; dSize - размер заполняемого блока
;-----------------------------------------------------
proc _fillmem lpDst:DWORD, dVal:DWORD, dSize:DWORD
pusha
cld
mov edi,[lpDst]
mov eax,[dVal]
; Оставить только младший байт
movzx eax,al
mov edx,01010101h
; Теперь в EAX будет DWORD-заполнитель
mul edx
mov ecx,[dSize]
push ecx
; Разделить на 4 и получить длину в DWORD
shr ecx,2
; Заполнить основную часть строки DWORD'ами
rep stosd
pop ecx
; Получить остаток от деления на 4
and ecx,3
; Заполнить остаток строки байтами
rep stosb
popa
ret
endp
Получение каталога запуска программы. В отличие от
GetCurrentDirectory получает именно тот каталог, в котором находится запущенный исполняемый файл. Этот код удобно применять для формирования полных путей к файлам конфигурации, каким-либо дополнительным ресурсам, хранящимся в папке с программой и т.п. Можно, конечно, использовать
GetFullPathName, но так получается быстрее и короче.
; my_path описан в данных как буфер размером MAX_PATH+1
mov edi,my_path
invoke GetModuleFileName,NULL,edi,MAX_PATH
add edi,eax
std
mov ecx,eax
mov al,'\'
repne scasb
mov byte [edi+2],0
cld
; Теперь в my_path содержится каталог запуска с завершающим '\'
Два варианта нахождения модуля числа, записанного в EAX:
; Вариант №1
@@: neg eax
js @b
; EAX = |EAX|
;------------------------------------
; Вариант №2
mov edx, eax
sar edx, 31
xor eax, edx
sub eax, edx
; EAX = |EAX|
Как поменять местами значения двух регистров без использования дополнительных переменных и других регистров.
; Идеальный вариант с использованием штатной команды XCHG
xchg eax,ebx
; Классический вариант с использованием стека
push eax
push ebx
pop eax
pop ebx
; Хитрый вариант, иногда встречается на олимпиадах по программированию
xor eax,ebx
xor ebx,eax
xor eax,ebx
Поиск наибольшего общего делителя (НОД) двух натуральных чисел. Если помните из курса школьной математики, то наибольший общий делитель - это наибольшее из чисел, на которые делится каждое из данных чисел без остатка. Очень красивый и высокооптимизированный алгоритм.
; EAX = первое число
; EDX = второе число
neg eax
jz loc_3
loc_1:
neg eax
xchg edx,eax
loc_2:
sub eax,edx
jg loc_2
jnz loc_1
loc_3:
add eax,edx
jnz loc_4
inc eax
loc_4:
; EAX = найденное значение НОД
Поиск наименьшего общего кратного (НОК) двух натуральных чисел. Наименьшим общим кратным называется наименьшее натуральное число, которое само делится на каждое из данных чисел без остатка.
; EAX = первое число
; EBX = второе число
cmp eax,ebx
je loc_3
jb loc_1
xchg eax,ebx
loc_1:
mov ecx,eax
loc_2:
add eax,ecx
push eax
xor edx,edx
div ebx
pop eax
or edx,edx
jnz loc_2
loc_3:
; EAX = найденное значение НОК
Продолжение следует, так что следите за обновлениями. Если у вас тоже есть какие-нибудь свои наработки, то можете поделиться ими в комментариях, самые интересные примеры я обязательно добавлю в статью.