Código de máquina x86_64, 4 bytes
¡La instrucción BSF (bit scan forward) hace exactamente esto !
0x0f 0xbc 0xc7 0xc3
En el ensamblaje de estilo gcc, esto es:
.globl f
f:
bsfl %edi, %eax
ret
La entrada se proporciona en el registro EDI y se devuelve en el registro EAX según las convenciones de llamadas c estándar de 64 bits .
Debido a la codificación binaria del complemento a dos, esto funciona para los números -ve y + ve.
Además, a pesar de la documentación que dice "Si el contenido del operando de origen es 0, el contenido del operando de destino no está definido". , Encuentro en mi Ubuntu VM que la salida de f(0)
es 0.
Instrucciones:
- Guarde lo anterior como
evenness.s
y arme congcc -c evenness.s -o evenness.o
- Guarde el siguiente controlador de prueba como
evenness-main.c
y compile con 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;
}
Entonces:
- Enlazar:
gcc evenness-main.o evenness.o -o evenness
- Correr:
./evenness
@FarazMasroor solicitó más detalles sobre cómo se obtuvo esta respuesta.
Estoy más familiarizado con c que las complejidades del ensamblaje x86, por lo que generalmente uso un compilador para generar código de ensamblaje para mí. Sé por experiencia que las extensiones gcc como __builtin_ffs()
, __builtin_ctz()
y__builtin_popcount()
típicamente compilan y ensamblan a 1 o 2 instrucciones en x86. Entonces comencé con una función c como:
int f(int n) {
return __builtin_ctz(n);
}
En lugar de usar la compilación gcc regular hasta el código objeto, puede usar la -S
opción de compilar solo para ensamblar - gcc -S -c evenness.c
. Esto da un archivo de ensamblaje 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
Mucho de esto se puede jugar al golf. En particular, sabemos que la convención de llamada c para funciones con firma es agradable y simple: el parámetro de entrada se pasa en el registro y el valor de retorno se devuelve en el registro. Por lo tanto, podemos extraer la mayoría de las instrucciones: muchas de ellas se preocupan por guardar registros y configurar un nuevo marco de pila. No usamos la pila aquí y solo usamos el registro, por lo que no debemos preocuparnos por otros registros. Esto deja el código de ensamblaje "golfizado":int f(int n);
EDI
EAX
EAX
.globl f
f:
bsfl %edi, %eax
ret
Tenga en cuenta que, como señala @zwol, también puede usar una compilación optimizada para lograr un resultado similar. En particular, -Os
produce exactamente las instrucciones anteriores (con algunas directivas de ensamblador adicionales que no producen ningún código de objeto adicional).
Esto ahora se ensambla con gcc -c evenness.s -o evenness.o
, que luego se puede vincular a un programa de controlador de prueba como se describe anteriormente.
Hay varias formas de determinar el código de máquina correspondiente a este ensamblaje. Mi favorito es usar el disass
comando de desmontaje 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)
Entonces podemos ver que el código de máquina para la bsf
instrucción es 0f bc c7
y para ret
es c3
.