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 에서처럼 어셈블리 언어는 목록이나 배열을 일급 유형으로 구현하지 않고 포인터와 길이의 조합으로 나타냅니다. 따라서이 함수가 두 개의 매개 변수를 받도록 구성했습니다. 첫 번째는 숫자 목록의 시작 부분에 대한 포인터이고 두 번째는 목록의 총 길이 (총 자릿수, 하나의 색인)를 지정하는 정수입니다. .
이 기능 은 Gnu / UNIX 시스템의 표준 인 System V AMD64 호출 규칙을 준수 합니다. 특히 첫 번째 매개 변수 (목록의 시작을 가리키는 포인터)가 전달됩니다 RDI
(64 비트 코드이므로 64 비트 포인터 임). 두 번째 매개 변수 (목록의 길이)는 ESI
( 이것은 32 비트 값일 뿐이며, 이는 연주하기에 충분한 자릿수 이상이므로 당연히 0이 아닌 것으로 가정합니다). 결과는 EAX
레지스터에 반환됩니다 .
더 명확하다면, 이것은 C 프로토 타입이 될 것입니다 (그리고 이것을 C에서 함수를 호출하는데 사용할 수 있습니다) :
int OddsAndEvens(int *ptrDigits, int length);
ungolfed 어셈블리 니모닉 :
; 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
레지스터를 0으로 만듭니다. EAX
레지스터는 클리어되고 XOR
자체 (2 바이트)와 함께 보내고, 다음 EDX
레지스터 사인 연장 그것으로 EAX (클리어되어 CDQ
, 1 바이트).
그런 다음 배열에 전달 된 모든 숫자를 반복하는 루프로 이동합니다. 숫자를 검색하고 짝수 또는 홀수인지 테스트합니다 (최소 유효 비트를 테스트하여 값이 짝수이면 0이거나 홀수이면 1 임). 적절한 누산기에 가치. 루프의 맨 아래에서 숫자 카운터 ( ESI
)를 줄이고 0이 아닌 한 (즉, 목록에 더 많은 자릿수가 남아있는 한) 계속 반복합니다.
여기서 까다로운 것은 x86에서 가능한 가장 복잡한 주소 지정 모드를 사용하는 초기 MOV 명령입니다. * 걸리는 RDI
기준 레지스터 (리스트의 시작에 대한 포인터), 저울로 RSI
(바이트 정수의 크기) 4 (인덱스로서 작용 길이 카운터)과 추가하는베이스 및 길이 카운터는 1을 기준으로하고 오프셋은 0을 기준으로해야하기 때문에 총계에서 4를 뺍니다. 이것은 배열의 숫자 주소를 제공 한 다음 ECX
레지스터에 로드됩니다 .
루프가 끝나면 짝수에서 확률을 뺍니다 ( EAX -= EDX
).
마지막으로, 대부분의 C 컴파일러가 abs
함수에 사용하는 것과 동일한 공통 트릭을 사용하여 절대 값을 계산합니다 . 이 트릭이 어떻게 작동하는지에 대해서는 자세히 다루지 않겠습니다. 힌트는 코드 주석을 참조하거나 웹 검색을 수행하십시오.
__
* 간단한 주소 지정 모드를 사용하도록 코드를 다시 작성할 수 있지만 더 짧게 만들지는 않습니다. RDI
루프를 통해 매번 8 씩 역 참조 하고 증가 시키는 대체 구현을 생각해 낼 수 있었지만 카운터를 계속 줄여야하기 때문에 ESI
동일한 30 바이트로 나타났습니다. 처음에 희망을 얻은 add eax, DWORD PTR [rdi]
것은 2 개의 등록 된 값을 추가하는 것과 동일한 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