codice macchina x86_64, 4 byte
L'istruzione BSF (bit scan forward) fa esattamente questo !
0x0f 0xbc 0xc7 0xc3
Nell'assembly in stile gcc, questo è:
.globl f
f:
bsfl %edi, %eax
ret
L'input viene fornito nel registro EDI e restituito nel registro EAX secondo le convenzioni di chiamata c standard a 64 bit .
A causa della codifica binaria del complemento a due, questo funziona per i numeri -ve e + ve.
Inoltre, nonostante la documentazione che dice "Se il contenuto dell'operando di origine è 0, il contenuto dell'operando di destinazione non è definito." , Trovo sulla mia macchina virtuale Ubuntu che l'output di f(0)
è 0.
Istruzioni:
- Salvare quanto sopra come
evenness.s
e assemblare congcc -c evenness.s -o evenness.o
- Salvare il seguente driver di test come
evenness-main.c
e compilare 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;
}
Poi:
- link:
gcc evenness-main.o evenness.o -o evenness
- Correre:
./evenness
@FarazMasroor ha chiesto maggiori dettagli su come è stata ottenuta questa risposta.
Conosco meglio c rispetto alle complessità dell'assembly x86, quindi in genere uso un compilatore per generare il codice assembly per me. So per esperienza che estensioni gcc come __builtin_ffs()
, __builtin_ctz()
e in__builtin_popcount()
genere compilano e assemblano in 1 o 2 istruzioni su x86. Quindi ho iniziato con una funzione c come:
int f(int n) {
return __builtin_ctz(n);
}
Invece di usare la normale compilazione gcc fino al codice oggetto, puoi usare l' -S
opzione per compilare solo in assembly - gcc -S -c evenness.c
. Questo dà un file assembly evenness.s
come questo:
.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
Molto di questo può essere risolto. In particolare sappiamo che la convenzione di chiamata c per funzioni con firma è semplice e piacevole: il parametro di input viene passato nel registro e il valore di ritorno viene restituito nel registro. Quindi possiamo prendere la maggior parte delle istruzioni: molte riguardano il salvataggio dei registri e l'impostazione di un nuovo stack frame. Non utilizziamo lo stack qui e utilizziamo solo il registro, quindi non è necessario preoccuparsi di altri registri. Questo lascia il codice assembly "golfed":int f(int n);
EDI
EAX
EAX
.globl f
f:
bsfl %edi, %eax
ret
Nota come sottolinea @zwol, puoi anche utilizzare una compilazione ottimizzata per ottenere un risultato simile. In particolare -Os
produce esattamente le istruzioni sopra (con alcune direttive addizionali sull'assemblatore che non producono alcun codice oggetto aggiuntivo).
Questo è ora assemblato con gcc -c evenness.s -o evenness.o
, che può quindi essere collegato in un programma del driver di test come descritto sopra.
Esistono diversi modi per determinare il codice macchina corrispondente a questo assieme. Il mio preferito è usare il disass
comando disassembly 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)
Quindi possiamo vedere che il codice macchina per l' bsf
istruzione è 0f bc c7
e per ret
è c3
.