x86_64-Maschinencode, 4 Byte
Der BSF-Befehl (Bit Scan Forward) macht genau das !
0x0f 0xbc 0xc7 0xc3
In der gcc-artigen Montage ist dies:
.globl f
f:
bsfl %edi, %eax
ret
Die Eingabe wird im EDI-Register angegeben und gemäß den üblichen 64-Bit- C- Aufrufkonventionen im EAX-Register zurückgegeben .
Aufgrund der Zweierkomplement-Binärcodierung funktioniert dies sowohl für -ve- als auch für + ve-Zahlen.
Auch trotz der Dokumentation "Wenn der Inhalt des Quelloperanden 0 ist, ist der Inhalt des Zieloperanden undefiniert." Finde ich auf meiner Ubuntu VM, dass die Ausgabe von f(0)
0 ist.
Anleitung:
- Speichern Sie das obige als
evenness.s
und montieren Sie mitgcc -c evenness.s -o evenness.o
- Speichern Sie den folgenden Testtreiber als
evenness-main.c
und kompilieren Sie mit 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;
}
Dann:
- Verknüpfung:
gcc evenness-main.o evenness.o -o evenness
- Lauf:
./evenness
@FarazMasroor bat um weitere Informationen darüber, wie diese Antwort abgeleitet wurde.
Da ich mit C besser vertraut bin als mit den Feinheiten von x86-Assemblys, benutze ich normalerweise einen Compiler, um Assembly-Code für mich zu generieren. Ich weiß aus Erfahrung , dass gcc Erweiterungen wie __builtin_ffs()
, __builtin_ctz()
und__builtin_popcount()
typischerweise zusammenzustellen und um 1 oder 2 Anweisungen auf x86 montieren. Also begann ich mit einer c- Funktion wie:
int f(int n) {
return __builtin_ctz(n);
}
Anstatt die reguläre GCC-Kompilierung bis hin zum Objektcode zu verwenden, können Sie die -S
Option verwenden, um nur Assembly zu kompilieren gcc -S -c evenness.c
. Dies ergibt eine Assembly-Datei evenness.s
wie folgt:
.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
Vieles davon kann ausgenutzt werden. Insbesondere wissen wir, dass die c- Aufrufkonvention für Funktionen mit int f(int n);
Signatur nett und einfach ist - der Eingabeparameter wird im EDI
Register übergeben und der Rückgabewert wird im EAX
Register zurückgegeben. So können wir die meisten Anweisungen herausnehmen - viele befassen sich mit dem Speichern von Registern und dem Einrichten eines neuen Stapelrahmens. Wir benutzen den Stack hier nicht und benutzen nur das EAX
Register, also brauchen wir uns nicht um andere Register zu kümmern. Dies hinterlässt den Assembler-Code "Golf":
.globl f
f:
bsfl %edi, %eax
ret
Beachten Sie, wie @zwol hervorhebt, dass Sie auch eine optimierte Kompilierung verwenden können, um ein ähnliches Ergebnis zu erzielen. -Os
Produziert insbesondere genau die obigen Anweisungen (mit ein paar zusätzlichen Assembler-Direktiven, die keinen zusätzlichen Objektcode produzieren.)
Diese wird nun mit zusammengesetzt gcc -c evenness.s -o evenness.o
, die dann wie oben beschrieben in ein Testtreiberprogramm eingebunden werden können.
Es gibt verschiedene Möglichkeiten, den dieser Baugruppe entsprechenden Maschinencode zu ermitteln. Mein Favorit ist die Verwendung des disass
Befehls gdb 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)
So können wir sehen, dass der Maschinencode für die bsf
Anweisung 0f bc c7
und für ret
ist c3
.