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
上面的代码定义了一个函数,该函数接受整数位的列表/数组,并返回其偶数位之和与奇数位之和之间的绝对差。
如在C,汇编语言不会将列表或数组实现为一类类型,而是将它们表示为指针和长度的组合。因此,我为该函数安排了两个参数:第一个是指向数字列表开头的指针,第二个是指定列表总长度的整数(数字总数,一个索引) 。
该函数符合System V 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
与其自身(2个字节)坏它,然后将EDX
寄存器由EAX进去(符号扩展清零CDQ
,1个字节)。
然后,我们进入循环遍历数组中传递的所有数字的循环。它检索一个数字,测试一下它是偶数还是奇数(通过测试最低有效位,如果该值是偶数则为0,如果它是奇数则为1),然后相应地跳过或掉入,值到适当的累加器。在循环的底部,我们递减数字计数器(ESI
)并继续循环,只要它不为零(即,只要列表中还有更多数字要检索)。
唯一棘手的是初始的MOV指令,该指令使用x86上可能的最复杂的寻址模式。*它以RDI
基址寄存器(指向列表开头的指针)为基数RSI
(将长度索引用作索引)乘以4(整数大小,以字节为单位),并将其加到基数上,然后然后从总数中减去4(因为长度计数器是基于1的,而我们需要将偏移量设为基于0的)。这给出了数组中数字的地址,然后将其加载到ECX
寄存器中。
循环结束后,我们从偶数(EAX -= EDX
)中减去几率。
最后,我们使用一个通用技巧来计算绝对值-大多数C编译器都使用该技巧abs
。我不会在这里详细介绍这种技巧的工作方式。查看代码注释以获取提示,或进行网络搜索。
__
*可以重写代码以使用更简单的寻址模式,但是并不能使其更短。我能够提出一个替代实现,该实现RDI
每次取消引用并在循环中将其递增8,但是由于您仍必须递减in中的计数器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