kod maszynowy x86_64, 4 bajty
Dokładnie to robi instrukcja BSF (przeskok bitów do przodu) !
0x0f 0xbc 0xc7 0xc3
W zespole w stylu gcc jest to:
.globl f
f:
bsfl %edi, %eax
ret
Dane wejściowe są podawane w rejestrze EDI i zwracane w rejestrze EAX zgodnie ze standardowymi 64-bitowymi konwencjami wywoływania c .
Ze względu na kodowanie binarne z dopełnieniem dwóch działa to zarówno dla liczb -ve, jak i + ve.
Ponadto, pomimo dokumentacji mówiącej: „Jeśli treść operandu źródłowego wynosi 0, treść operandu docelowego jest niezdefiniowana”. , Znajduję na mojej maszynie Wirtualnej Ubuntu, że wynikiem f(0)
jest 0.
Instrukcje:
- Zapisz powyższe
evenness.s
i zmontujgcc -c evenness.s -o evenness.o
- Zapisz następujący sterownik testowy jako
evenness-main.c
i skompiluj z 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;
}
Następnie:
- Połączyć:
gcc evenness-main.o evenness.o -o evenness
- Biegać:
./evenness
@FarazMasroor poprosił o więcej informacji na temat sposobu uzyskania tej odpowiedzi.
Jestem bardziej zaznajomiony z c niż zawiłościami zestawu x86, więc zazwyczaj używam kompilatora do generowania kodu zestawu dla mnie. Wiem z doświadczenia, że rozszerzenia takie jak gcc __builtin_ffs()
, __builtin_ctz()
i__builtin_popcount()
zazwyczaj skompilować i zmontować do 1 lub 2 instrukcje x86. Zacząłem więc od funkcji c, takiej jak:
int f(int n) {
return __builtin_ctz(n);
}
Zamiast używać zwykłej kompilacji gcc aż do kodu obiektowego, możesz użyć -S
opcji kompilacji tylko do złożenia - gcc -S -c evenness.c
. To daje plik asemblera evenness.s
taki jak ten:
.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
Wiele z tego można zagrać w golfa. W szczególności wiemy, że konwencja wywoływania c dla funkcji z podpisem jest ładna i prosta - parametr wejściowy jest przekazywany do rejestru, a zwracana wartość jest zwracana do rejestru. Możemy więc wyciągnąć większość instrukcji - wiele z nich dotyczy zapisywania rejestrów i konfigurowania nowej ramki stosu. Nie używamy tutaj stosu i używamy tylko rejestru, więc nie musisz się martwić o inne rejestry. Pozostawia to kod zestawu „golfowy”:int f(int n);
EDI
EAX
EAX
.globl f
f:
bsfl %edi, %eax
ret
Uwaga: jak wskazuje @zwol, można również użyć zoptymalizowanej kompilacji, aby osiągnąć podobny wynik. W szczególności -Os
tworzy dokładnie powyższe instrukcje (z kilkoma dodatkowymi dyrektywami asemblera, które nie generują żadnego dodatkowego kodu obiektowego).
Jest to teraz zmontowane z gcc -c evenness.s -o evenness.o
, które można następnie połączyć z programem sterownika testowego, jak opisano powyżej.
Istnieje kilka sposobów ustalenia kodu maszynowego odpowiadającego temu zespołowi. Moim ulubionym jest użycie disass
polecenia dezasemblacji gdb :
$ 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)
Widzimy więc, że kod maszynowy bsf
instrukcji jest 0f bc c7
i ret
jest c3
.