x86_64 code machine, 4 octets
L'instruction BSF (bit scan forward) fait exactement cela !
0x0f 0xbc 0xc7 0xc3
En assembleur de style gcc, ceci est:
.globl f
f:
bsfl %edi, %eax
ret
L'entrée est donnée dans le registre de l' EDI et renvoyé dans le registre EAX selon la norme 64 bits c conventions d' appel.
En raison de l’encodage binaire à complément à deux, cela fonctionne pour les nombres aussi bien que les +.
En outre, malgré la documentation qui dit "Si le contenu de l'opérande source est 0, le contenu de l'opérande de destination est indéfini". , Je trouve sur ma machine virtuelle Ubuntu que la sortie f(0)
est 0.
Instructions:
- Enregistrer ce qui précède
evenness.s
et assembler avecgcc -c evenness.s -o evenness.o
- Enregistrez le pilote de test suivant sous
evenness-main.c
et compilez avec 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;
}
Ensuite:
- Lien:
gcc evenness-main.o evenness.o -o evenness
- Courir:
./evenness
@FarazMasroor a demandé plus de détails sur la manière dont cette réponse a été obtenue.
C étant plus familier avec les subtilités de l’assemblage x86, j’utilise généralement un compilateur pour générer le code d’assemblage. Je sais par expérience que les extensions gcc telles que __builtin_ffs()
, __builtin_ctz()
et__builtin_popcount()
généralement compiler et assembler 1 ou 2 instructions sur x86. J'ai donc commencé avec une fonction c comme:
int f(int n) {
return __builtin_ctz(n);
}
Au lieu d’utiliser une compilation gcc normale jusqu’au code objet, vous pouvez utiliser l’ -S
option de compilation juste pour assembler - gcc -S -c evenness.c
. Cela donne un fichier d'assemblage evenness.s
comme ceci:
.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
Beaucoup de cela peut être joué au golf. En particulier, nous savons que la convention d'appel c pour les fonctions avec signature est simple et agréable: le paramètre d'entrée est passé dans le registre et la valeur de retour est renvoyée dans le registre. Nous pouvons donc extraire la plupart des instructions - beaucoup d’entre elles concernent la sauvegarde des registres et la configuration d’un nouveau cadre de pile. Nous n'utilisons pas la pile ici, mais uniquement le registre, vous n'avez donc pas à vous soucier des autres registres. Cela laisse le code de montage "golfé":int f(int n);
EDI
EAX
EAX
.globl f
f:
bsfl %edi, %eax
ret
Notez que @zwol fait remarquer que vous pouvez également utiliser une compilation optimisée pour obtenir un résultat similaire. En particulier, -Os
produit exactement les instructions ci-dessus (avec quelques directives d'assembleur supplémentaires qui ne produisent pas de code objet supplémentaire).
Ceci est maintenant assemblé avec gcc -c evenness.s -o evenness.o
, qui peut ensuite être lié à un programme de pilote de test comme décrit ci-dessus.
Il existe plusieurs façons de déterminer le code machine correspondant à cet assemblage. Mon préféré est d'utiliser la disass
commande de désassemblage de 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)
Nous pouvons donc voir que le code machine pour l' bsf
instruction est 0f bc c7
et pour ret
est c3
.