машинный код x86_64, 4 байта
Инструкция BSF (bit scan forward) делает именно это !
0x0f 0xbc 0xc7 0xc3
В сборке в стиле gcc это:
.globl f
f:
bsfl %edi, %eax
ret
Входные данные передаются в регистр EDI и возвращаются в регистр EAX в соответствии со стандартными 64-битными соглашениями о вызовах c .
Из-за двоичного кодирования, дополняющего два, это работает как для чисел -ve, так и для чисел + ve.
Кроме того, несмотря на документацию, в которой говорится «Если содержимое исходного операнда равно 0, содержимое целевого операнда не определено». На моей виртуальной машине Ubuntu я обнаружил, что вывод f(0)
равен 0.
Инструкции:
- Сохраните все как
evenness.s
и соберитеgcc -c evenness.s -o evenness.o
- Сохраните следующий тестовый драйвер как
evenness-main.c
и скомпилируйте с 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;
}
Затем:
- Ссылка:
gcc evenness-main.o evenness.o -o evenness
- Бегать:
./evenness
@FarazMasroor попросил более подробную информацию о том, как этот ответ был получен.
Я больше знаком с c, чем со сложностями сборки x86, поэтому обычно я использую компилятор для генерации кода сборки для себя. Я знаю по опыту , что НКУ расширение , такие как __builtin_ffs()
, __builtin_ctz()
и__builtin_popcount()
как правило , компилировать и смонтируют 1 или 2 инструкции по x86. Итак, я начал с функции c, например:
int f(int n) {
return __builtin_ctz(n);
}
Вместо того чтобы использовать обычную компиляцию gcc вплоть до объектного кода, вы можете использовать -S
опцию для компиляции только в сборку - gcc -S -c evenness.c
. Это дает файл сборки evenness.s
следующим образом:
.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
Многое из этого можно сыграть в гольф. В частности, мы знаем, что соглашение о вызовах c для функций с сигнатурой приятно и просто - входной параметр передается в регистр, а возвращаемое значение возвращается в регистр. Таким образом, мы можем извлечь большинство инструкций - многие из них связаны с сохранением регистров и настройкой нового стекового фрейма. Здесь мы не используем стек и используем только регистр, поэтому не нужно беспокоиться о других регистрах. Это оставляет "гольф" код сборки:int f(int n);
EDI
EAX
EAX
.globl f
f:
bsfl %edi, %eax
ret
Обратите внимание, что, как указывает @zwol, вы также можете использовать оптимизированную компиляцию для достижения аналогичного результата. В частности, -Os
производит точно такие же инструкции (с несколькими дополнительными директивами на ассемблере, которые не создают никакого дополнительного объектного кода).
Теперь он собран gcc -c evenness.s -o evenness.o
, который затем может быть связан с программой тестового драйвера, как описано выше.
Есть несколько способов определить машинный код, соответствующий этой сборке. Мой любимый - использовать команду gdb disass
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)
Итак, мы видим, что машинный код для bsf
инструкции есть 0f bc c7
и для ret
есть c3
.