x86-64 Makine Kodu, 30 bayt
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
Yukarıdaki kod, bir tamsayı hane listesini / dizisini kabul eden ve çift hane toplamı ile tek hane toplamı arasındaki mutlak farkı döndüren bir işlevi tanımlar.
C'deki gibi dilinde , montaj dili listeleri veya dizileri birinci sınıf türleri olarak uygulamaz, aksine bunları bir işaretçi ve uzunluk kombinasyonu olarak temsil eder. Bu nedenle, bu işlevin iki parametreyi kabul etmesi için ayarladım: birincisi basamak listesinin başlangıcına bir işaretçi ve ikincisi listenin toplam uzunluğunu belirten bir tamsayıdır (toplam basamak sayısı, tek dizinli) .
İşlev , Gnu / UNIX sistemlerinde standart olan System V AMD64 çağrı kuralına uygundur . Özellikle, ilk parametre (listenin başına işaretçi) geçirilir RDI
(64 bit kod olduğu için 64 bitlik bir işaretçi) ve ikinci parametre (listenin uzunluğu) iletilir ESI
( bu sadece 32 bitlik bir değerdir, çünkü bu, oynamak için yeterli basamaktan fazladır ve doğal olarak sıfır olmadığı varsayılır). Sonuç EAX
kayıt defterine döndürülür .
Daha açıksa, bu C prototipi olacaktır (ve işlevi C'den çağırmak için bunu kullanabilirsiniz):
int OddsAndEvens(int *ptrDigits, int length);
Ungolfed montaj anımsatıcıları:
; 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
İşte kodun kısa bir özeti:
- İlk olarak, çift ve tek rakamların toplamını tutmak için kullanılacak
EAX
ve EDX
kayıtlarını sıfırlarız . EAX
Yazmaç ile temizlenir XOR
başına (2 byte) ile ing ve EDX
yazmaç işareti uzanan içine EAX ile (temizlenir CDQ
, 1 bayt).
Ardından, dizide geçirilen tüm basamakları yineleyen döngüye giriyoruz. Bir rakam alır, çift veya tek olup olmadığını test eder (en küçük anlamlı biti test ederek, değer çiftse 0 veya tek ise 1 olur) ve ardından buna göre atlar veya düşer uygun akümülatöre Döngünün altında, basamak sayacını ( ESI
) azaltır ve sıfır olmadığı sürece (yani, alınacak listede daha fazla basamak kaldığı sürece) döngüye devam ederiz .
Burada zor olan tek şey, x86'da mümkün olan en karmaşık adresleme modunu kullanan ilk MOV talimatıdır. * Bu alan RDI
taban kaydının (listenin başına işaretçi), terazi olarak RSI
(bayt, bir tamsayı büyüklüğü) 4 ile (indir olarak uzunluğu sayacı) ve ekler tabanına ve daha sonra toplamdan 4 çıkarır (çünkü uzunluk sayacı tek tabanlıdır ve ofsetin sıfır tabanlı olması gerekir). Bu, daha sonra ECX
kayıt defterine yüklenen dizideki basamağın adresini verir .
Döngü bittikten sonra, oranların eşitlerden ( EAX -= EDX
) çıkarılmasını sağlıyoruz.
Son olarak, mutlak değeri ortak bir hile kullanarak hesaplıyoruz - abs
fonksiyon için çoğu C derleyicisi tarafından kullanılanla aynı . Bu hilenin burada nasıl çalıştığı hakkında ayrıntılara girmeyeceğim; ipuçları için kod yorumlarına bakın veya bir web araması yapın.
__
* Kod, daha basit adresleme modlarını kullanmak için yeniden yazılabilir, ancak bu kodu kısaltmaz. RDI
Döngü boyunca her seferinde 8 tarafından referansı kaldırılan ve arttıran alternatif bir uygulama geliştirebildim , ancak yine de sayacı azaltmanız gerektiğinden ESI
, bu aynı 30 bayt olduğu ortaya çıktı. Başlangıçta bana umut veren add eax, DWORD PTR [rdi]
şey, sadece 2 bayt, iki kayıtlı değer eklemekle aynı. İşte bana, biraz çaba sarf etmeye çalışan herkes kurtarmak için bu uygulama :-)
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