Машинный код x86-64, 30 байт
31 C0 99 8B 4C B7 FC F6 C1 01 74 04 01 CA EB 02 01 C8 FF CE 75 ED 29 D0 99 31 D0 29 D0 C3
Приведенный выше код определяет функцию, которая принимает список / массив целочисленных цифр и возвращает абсолютную разницу между суммой четных цифр и суммой нечетных цифр.
Как в С , язык ассемблера не реализует списки или массивы как первоклассные типы, а скорее представляет их как комбинацию указателя и длины. Поэтому я организовал для этой функции прием двух параметров: первый - указатель на начало списка цифр, а второй - целое число, определяющее общую длину списка (общее количество цифр, одноиндексированное) ,
Функция соответствует соглашению о вызовах System64 AMD64 , стандартному для систем Gnu / UNIX. В частности, первый параметр (указатель на начало списка) передается RDI
(так как это 64-разрядный код, это 64-разрядный указатель), а второй параметр (длина списка) передается в ESI
( это всего лишь 32-битное значение, потому что для этого более чем достаточно цифр, и, естественно, предполагается, что оно ненулевое). Результат возвращается в EAX
регистр.
Если это будет более понятным, это будет прототип C (и вы можете использовать это для вызова функции из C):
int OddsAndEvens(int *ptrDigits, int length);
Неуправляемая сборка мнемоники:
; parameter 1 (RDI) == pointer to list of integer digits
; parameter 2 (ESI) == number of integer digits in list (assumes non-zero, of course)
OddsAndEvens:
xor eax, eax ; EAX = 0 (accumulator for evens)
cdq ; EDX = 0 (accumulator for odds)
.IterateDigits:
mov ecx, [rdi+rsi*4-4] ; load next digit from list
test cl, 1 ; test last bit to see if even or odd
jz .IsEven ; jump if last bit == 0 (even)
.IsOdd: ; fall through if last bit != 0 (odd)
add edx, ecx ; add value to odds accumulator
jmp .Continue ; keep looping
.IsEven:
add eax, ecx ; add value to evens accumulator
.Continue: ; fall through
dec esi ; decrement count of digits in list
jnz .IterateDigits ; keep looping as long as there are digits left
sub eax, edx ; subtract odds accumulator from evens accumulator
; abs
cdq ; sign-extend EAX into EDX
xor eax, edx ; XOR sign bit in with the number
sub eax, edx ; subtract sign bit
ret ; return with final result in EAX
Вот краткий обзор кода:
- Во- первых, мы обнулить
EAX
и EDX
регистры, которые будут использоваться для хранения сумм суммы четных и нечетных цифр. EAX
Регистр очищается от XOR
ING его с собой (2 байта), а затем EDX
регистр очищается от входа продлевая EAX в него ( CDQ
1 байт).
Затем мы идем в цикл, который перебирает все цифры, передаваемые в массиве. Он извлекает цифру, проверяет, является ли она четным или нечетным (проверяя младший значащий бит, который будет равен 0, если значение является четным, или 1, если он нечетный), а затем, соответственно, перепрыгивает или падает, добавляя, что значение для соответствующего аккумулятора. В нижней части цикла мы уменьшаем счетчик цифр ( ESI
) и продолжаем цикл до тех пор, пока он не равен нулю (т. Е. До тех пор, пока в списке осталось больше цифр, которые нужно извлечь).
Единственная сложность здесь - это начальная инструкция MOV, которая использует наиболее сложный режим адресации, возможный на x86. * Он принимает RDI
в качестве базового регистра (указатель на начало списка), масштабирует RSI
(счетчик длины, который служит индексом) на 4 (размер целого числа в байтах) и добавляет его к базе, и затем вычитает 4 из общей суммы (потому что счетчик длины основан на единице, и нам нужно, чтобы смещение было на основе нуля). Это дает адрес цифры в массиве, который затем загружается в ECX
регистр.
После завершения цикла мы делаем вычитание шансов из четных ( EAX -= EDX
).
Наконец, мы вычисляем абсолютное значение, используя общий прием - тот же, который используется большинством компиляторов Си для abs
функции. Я не буду вдаваться в подробности о том, как этот трюк работает здесь; см. комментарии к коду для подсказок или выполните поиск в Интернете.
__
* Код можно переписать, чтобы использовать более простые режимы адресации, но это не делает его короче. Я смог придумать альтернативную реализацию, которая разыменовывала RDI
и увеличивала его на 8 каждый раз в цикле, но, поскольку вам все еще нужно уменьшить счетчик ESI
, это оказалось теми же 30 байтами. То, что изначально дало мне надежду, это то, что add eax, DWORD PTR [rdi]
это всего 2 байта, то же самое, что добавить два зарегистрированных значения. Вот эта реализация, хотя бы для того, чтобы спасти любого, кто пытается переиграть меня :-)
OddsAndEvens_Alt:
31 C0 xor eax, eax
99 cdq
.IterateDigits:
F6 07 01 test BYTE PTR [rdi], 1
74 04 je .IsEven
.IsOdd:
03 17 add edx, DWORD PTR [rdi]
EB 02 jmp .Continue
.IsEven:
03 07 add eax, DWORD PTR [rdi]
.Continue:
48 83 C7 08 add rdi, 8
FF CE dec esi
75 ED jne .IterateDigits
29 D0 sub eax, edx
99 cdq
31 D0 xor eax, edx
29 D0 sub eax, edx
C3 ret