Mã máy x86-64, 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
Đoạn mã trên xác định hàm chấp nhận danh sách / mảng các chữ số nguyên và trả về chênh lệch tuyệt đối giữa tổng các chữ số chẵn và tổng các chữ số lẻ của nó.
Như trong C , ngôn ngữ hợp ngữ không triển khai danh sách hoặc mảng dưới dạng loại hạng nhất, mà đại diện cho chúng dưới dạng kết hợp của một con trỏ và độ dài. Do đó, tôi đã sắp xếp cho hàm này chấp nhận hai tham số: đầu tiên là con trỏ đến đầu danh sách các chữ số và thứ hai là một số nguyên chỉ định tổng chiều dài của danh sách (tổng số chữ số, một chỉ mục) .
Hàm này tuân theo quy ước gọi AMD64 của System V , là tiêu chuẩn trên các hệ thống Gnu / UNIX. Cụ thể, tham số đầu tiên (con trỏ đến đầu danh sách) được truyền vào RDI
(vì đây là mã 64 bit, nó là con trỏ 64 bit) và tham số thứ hai (độ dài của danh sách) được truyền vào ESI
( đây chỉ là một giá trị 32 bit, bởi vì đó là quá nhiều chữ số để chơi và tự nhiên nó được coi là khác không). Kết quả được trả về trongEAX
sổ đăng ký.
Nếu nó rõ ràng hơn, đây sẽ là nguyên mẫu C (và bạn có thể sử dụng hàm này để gọi hàm từ C):
int OddsAndEvens(int *ptrDigits, int length);
Ma thuật lắp ráp bất khả xâm phạm:
; 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
Dưới đây là hướng dẫn ngắn gọn về mã:
- Đầu tiên, chúng tôi loại bỏ các thanh ghi
EAX
và EDX
thanh ghi, chúng sẽ được sử dụng để giữ tổng số của các chữ số chẵn và lẻ. Thanh EAX
ghi bị xóa bằng cách nhập XOR
chính nó (2 byte), và sau đó thanh EDX
ghi bị xóa bằng cách đăng nhập mở rộng EAX vào nó ( CDQ
, 1 byte).
Sau đó, chúng ta đi vào vòng lặp lặp qua tất cả các chữ số được truyền trong mảng. Nó lấy một chữ số, kiểm tra xem nó là số chẵn hay lẻ (bằng cách kiểm tra bit có ý nghĩa nhỏ nhất, sẽ là 0 nếu giá trị là chẵn hoặc 1 nếu là số lẻ), sau đó nhảy hoặc rơi qua đó, thêm vào đó giá trị cho bộ tích lũy thích hợp. Ở dưới cùng của vòng lặp, chúng tôi giảm bộ đếm chữ số ( ESI
) và tiếp tục lặp miễn là nó khác không (nghĩa là miễn là còn nhiều chữ số trong danh sách cần lấy).
Điều duy nhất khó khăn ở đây là hướng dẫn MOV ban đầu, sử dụng chế độ địa chỉ phức tạp nhất có thể có trên x86. * Nó lấy RDI
làm thanh ghi cơ sở (con trỏ đến đầu danh sách), chia tỷ lệ RSI
(bộ đếm độ dài, đóng vai trò là chỉ số) bằng 4 (kích thước của một số nguyên, tính bằng byte) và thêm nó vào cơ sở và sau đó trừ 4 khỏi tổng số (vì bộ đếm độ dài là dựa trên một và chúng ta cần phần bù là 0). Điều này cho địa chỉ của chữ số trong mảng, sau đó được tải vào thanh ECX
ghi.
Sau khi vòng lặp kết thúc, chúng tôi thực hiện phép trừ các tỷ lệ cược từ evens ( EAX -= EDX
).
Cuối cùng, chúng tôi tính toán giá trị tuyệt đối bằng cách sử dụng một thủ thuật phổ biến, cùng một cách sử dụng bởi hầu hết các trình biên dịch C cho abs
hàm. Tôi sẽ không đi vào chi tiết về cách thức hoạt động của thủ thuật này ở đây; xem bình luận mã cho gợi ý, hoặc thực hiện tìm kiếm trên web.
__
* Mã có thể được viết lại để sử dụng các chế độ địa chỉ đơn giản hơn, nhưng nó không làm cho nó ngắn hơn. Tôi đã có thể đưa ra một triển khai thay thế đã hủy đăng ký RDI
và tăng nó lên 8 lần mỗi lần qua vòng lặp, nhưng vì bạn vẫn phải giảm số lượt truy cập ESI
, nên hóa ra là 30 byte giống nhau. Điều ban đầu mang lại cho tôi hy vọng là add eax, DWORD PTR [rdi]
chỉ có 2 byte, giống như việc thêm hai giá trị được đăng ký. Đây là cách thực hiện, nếu chỉ để cứu bất kỳ ai đang cố gắng vượt qua tôi một số nỗ lực :-)
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