x86_64 기계 코드, 4 바이트
BSF (비트 스캔 포워드) 명령은 정확히 이것을 수행합니다 !
0x0f 0xbc 0xc7 0xc3
gcc 스타일 어셈블리에서 이것은 다음과 같습니다.
.globl f
f:
bsfl %edi, %eax
ret
입력은 EDI 레지스터에 제공되며 표준 64 비트 c 호출 규칙 에 따라 EAX 레지스터에 반환됩니다 .
2의 보수 이진 인코딩으로 인해 -ve 및 + ve 숫자에 대해 작동합니다.
또한 "소스 피연산자의 내용이 0이면 대상 피연산자의 내용이 정의되어 있지 않습니다." 라는 문서에도 불구하고 . 우분투 VM에서 출력 f(0)
이 0 이라는 것을 알았습니다 .
명령:
- 위와 같이 저장
evenness.s
하고 조립 하십시오gcc -c evenness.s -o evenness.o
- 다음 테스트 드라이버를 다른 이름으로 저장하고 다음으로
evenness-main.c
컴파일하십시오 gcc -c evenness-main.c -o evenness-main.o
.
#include <stdio.h>
extern int f(int n);
int main (int argc, char **argv) {
int i;
int testcases[] = { 14, 20, 94208, 7, 0, -4 };
for (i = 0; i < sizeof(testcases) / sizeof(testcases[0]); i++) {
printf("%d, %d\n", testcases[i], f(testcases[i]));
}
return 0;
}
그때:
- 링크:
gcc evenness-main.o evenness.o -o evenness
- 운영:
./evenness
@FarazMasroor는이 답변의 도출 방법에 대한 자세한 내용을 문의했습니다.
나는 x86 어셈블리의 복잡한 것보다 c에 더 익숙 하므로 일반적으로 컴파일러를 사용하여 어셈블리 코드를 생성합니다. 나는 것을 경험을 통해 알고 GCC의 같은 확장 __builtin_ffs()
, __builtin_ctz()
그리고__builtin_popcount()
일반적으로 컴파일 및 x86 1 개 또는 2 지침을 조립한다. 그래서 다음과 같은 c 함수로 시작했습니다 .
int f(int n) {
return __builtin_ctz(n);
}
일반 gcc 컴파일을 객체 코드까지 사용하는 대신이 -S
옵션을 사용하여 어셈블리로 컴파일 할 수 있습니다 gcc -S -c evenness.c
. 이것은 다음 evenness.s
과 같은 어셈블리 파일을 제공합니다 .
.file "evenness.c"
.text
.globl f
.type f, @function
f:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl %edi, -4(%rbp)
movl -4(%rbp), %eax
rep bsfl %eax, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size f, .-f
.ident "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.1) 4.8.4"
.section .note.GNU-stack,"",@progbits
이 중 많은 부분을 골라 낼 수 있습니다. 특히 서명이있는 함수에 대한 c 호출 규칙int f(int n);
이 훌륭하고 단순 하다는 것을 알고 있습니다 . 입력 매개 변수가 EDI
레지스터에 전달되고 리턴 값이 EAX
레지스터에 리턴됩니다 . 따라서 우리는 대부분의 명령어를 제거 할 수 있습니다. 많은 명령어는 레지스터를 저장하고 새로운 스택 프레임을 설정하는 것과 관련이 있습니다. 여기서는 스택을 사용하지 않고 EAX
레지스터 만 사용 하므로 다른 레지스터에 대해 걱정할 필요가 없습니다. 이것은 "골프 된"어셈블리 코드를 남깁니다.
.globl f
f:
bsfl %edi, %eax
ret
@zwol가 지적했듯이 최적화 된 컴파일을 사용하여 비슷한 결과를 얻을 수도 있습니다. 특히 -Os
위의 명령어를 생성합니다 (추가 객체 코드를 생성하지 않는 몇 가지 추가 어셈블러 지시문 사용).
이제와 함께 어셈블되어 gcc -c evenness.s -o evenness.o
위에서 설명한대로 테스트 드라이버 프로그램에 링크 될 수 있습니다.
이 어셈블리에 해당하는 기계어 코드를 결정하는 몇 가지 방법이 있습니다. 내가 가장 좋아하는 것은 gdb disass
disassembly 명령 을 사용하는 것입니다 .
$ gdb ./evenness
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1
...
Reading symbols from ./evenness...(no debugging symbols found)...done.
(gdb) disass /r f
Dump of assembler code for function f:
0x00000000004005ae <+0>: 0f bc c7 bsf %edi,%eax
0x00000000004005b1 <+3>: c3 retq
0x00000000004005b2 <+4>: 66 2e 0f 1f 84 00 00 00 00 00 nopw %cs:0x0(%rax,%rax,1)
0x00000000004005bc <+14>: 0f 1f 40 00 nopl 0x0(%rax)
End of assembler dump.
(gdb)
따라서 bsf
명령어 의 머신 코드 가 0f bc c7
and for ret
임을 알 수 c3
있습니다.