código de máquina x86_64, 4 bytes
A instrução BSF (bit scan forward) faz exatamente isso !
0x0f 0xbc 0xc7 0xc3
Na montagem no estilo gcc, é:
.globl f
f:
bsfl %edi, %eax
ret
A entrada é fornecida no registro EDI e retornada no registro EAX de acordo com as convenções padrão de chamada c de 64 bits .
Por causa da codificação binária do complemento de dois, isso funciona para os números -ve e + ve.
Além disso, apesar da documentação dizendo "Se o conteúdo do operando de origem for 0, o conteúdo do operando de destino será indefinido". , Acho na minha VM do Ubuntu que a saída f(0)
é 0.
Instruções:
- Guarde o que precede
evenness.s
e monte comgcc -c evenness.s -o evenness.o
- Salve o seguinte driver de teste
evenness-main.c
e compile com 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;
}
Então:
- Ligação:
gcc evenness-main.o evenness.o -o evenness
- Corre:
./evenness
O @FarazMasroor pediu mais detalhes sobre como essa resposta foi obtida.
Eu estou mais familiarizado com c do que com os meandros do assembly x86, então normalmente eu uso um compilador para gerar código de assembly para mim. Eu sei por experiência que as extensões do CCG como __builtin_ffs()
, __builtin_ctz()
e__builtin_popcount()
tipicamente compilar e montar a 1 ou 2 instruções sobre x86. Então eu comecei com uma função c como:
int f(int n) {
return __builtin_ctz(n);
}
Em vez de usar a compilação gcc regular até o código do objeto, você pode usar a -S
opção para compilar apenas para montagem - gcc -S -c evenness.c
. Isso fornece um arquivo de montagem evenness.s
como este:
.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
Muito disso pode ser jogado fora. Em particular, sabemos que a convenção de chamada c para funções com assinatura é agradável e simples - o parâmetro de entrada é passado no registrador e o valor retornado é retornado no registrador. Portanto, podemos tirar a maioria das instruções - muitas delas estão preocupadas em salvar registros e configurar um novo quadro de pilha. Nós não usamos a pilha aqui e apenas o registro, portanto, não precisa se preocupar com outros registros. Isso deixa o código de montagem "golfed":int f(int n);
EDI
EAX
EAX
.globl f
f:
bsfl %edi, %eax
ret
Observe como o @zwol aponta, você também pode usar a compilação otimizada para obter um resultado semelhante. Em particular, -Os
produz exatamente as instruções acima (com algumas diretivas de montagem adicionais que não produzem nenhum código de objeto extra).
Agora isso é montado gcc -c evenness.s -o evenness.o
, o qual pode ser vinculado a um programa de driver de teste, conforme descrito acima.
Existem várias maneiras de determinar o código da máquina correspondente a esta montagem. Meu favorito é usar o disass
comando 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)
Portanto, podemos ver que o código da máquina para a bsf
instrução é 0f bc c7
e para ret
é c3
.