x86-64 Kode Mesin, 30 byte
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
Kode di atas mendefinisikan fungsi yang menerima daftar / array angka integer dan mengembalikan perbedaan absolut antara jumlah digit genapnya dan jumlah digit ganjilnya.
Seperti dalam C , bahasa assembly tidak mengimplementasikan daftar atau array sebagai tipe kelas satu, melainkan merepresentasikannya sebagai kombinasi dari sebuah pointer dan panjang. Oleh karena itu, saya telah mengatur agar fungsi ini menerima dua parameter: yang pertama adalah penunjuk ke awal daftar digit, dan yang kedua adalah integer yang menentukan panjang total daftar (jumlah total digit, satu-diindeks) .
Fungsi ini sesuai dengan konvensi pemanggilan Sistem V AMD64 , yang merupakan standar pada sistem Gnu / UNIX. Secara khusus, parameter pertama (pointer ke awal daftar) dilewatkan RDI
(karena ini adalah kode 64-bit, itu adalah pointer 64-bit), dan parameter kedua (panjang daftar) diteruskan ke ESI
( ini hanya nilai 32-bit, karena itu lebih dari cukup angka untuk dimainkan, dan secara alami diasumsikan tidak nol). Hasilnya dikembalikan dalam EAX
register.
Jika lebih jelas, ini akan menjadi prototipe C (dan Anda dapat menggunakan ini untuk memanggil fungsi dari C):
int OddsAndEvens(int *ptrDigits, int length);
Mnemonik perakitan tidak dikumpulkan:
; 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
Berikut ini penjelasan singkat dari kode tersebut:
- Pertama, kita nolkan
EAX
dan EDX
register, yang akan digunakan untuk menyimpan jumlah total angka genap dan ganjil. The EAX
Register dibersihkan oleh XOR
ing dengan sendirinya (2 byte), dan kemudian EDX
register dibersihkan oleh tanda-memperpanjang EAX ke dalamnya ( CDQ
, 1 byte).
Kemudian, kita masuk ke loop yang berulang melalui semua digit yang dilewatkan dalam array. Ini mengambil digit, tes untuk melihat apakah itu genap atau ganjil (dengan menguji bit paling tidak signifikan, yang akan menjadi 0 jika nilainya genap atau 1 jika itu ganjil), dan kemudian melompat atau turun sesuai, menambahkan bahwa nilai ke akumulator yang sesuai. Di bagian bawah loop, kami mengurangi digit digit ( ESI
) dan melanjutkan looping selama tidak nol (yaitu, selama ada lebih banyak digit yang tersisa dalam daftar yang akan diambil).
Satu-satunya hal yang rumit di sini adalah instruksi MOV awal, yang menggunakan mode pengalamatan paling kompleks yang dimungkinkan pada x86. * Dibutuhkan RDI
sebagai register dasar (pointer ke awal daftar), skala RSI
(penghitung panjang, yang berfungsi sebagai indeks) oleh 4 (ukuran bilangan bulat, dalam byte) dan menambahkan itu ke basis, dan kemudian kurangi 4 dari total (karena penghitung panjang berbasis satu dan kita perlu offset menjadi berbasis nol). Ini memberikan alamat digit dalam array, yang kemudian dimuat ke dalam ECX
register.
Setelah loop selesai, kami melakukan pengurangan odds dari evens ( EAX -= EDX
).
Akhirnya, kita menghitung nilai absolut menggunakan trik umum — yang sama dengan yang digunakan oleh kebanyakan kompiler C untuk abs
fungsi. Saya tidak akan merinci bagaimana trik ini bekerja di sini; lihat komentar kode untuk petunjuk, atau lakukan pencarian web.
__
* Kode dapat ditulis ulang untuk menggunakan mode pengalamatan yang lebih sederhana, tetapi tidak membuatnya lebih pendek. Saya dapat membuat implementasi alternatif yang mengurangi RDI
dan menambahnya setiap 8 kali melalui loop, tetapi karena Anda masih harus mengurangi penghitung ESI
, ini ternyata 30 byte yang sama. Apa yang awalnya memberi saya harapan adalah add eax, DWORD PTR [rdi]
hanya 2 byte, sama dengan menambahkan dua nilai yang terdaftar. Inilah implementasi itu, jika hanya untuk menyelamatkan siapa pun yang mencoba mengungguli saya beberapa usaha :-)
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