машинний код x86_64, 4 байти
Інструкція BSF (бітове сканування вперед) робить саме це !
0x0f 0xbc 0xc7 0xc3
У зборах у стилі gcc це:
.globl f
f:
bsfl %edi, %eax
ret
Вхід подається в регістр EDI і повертається в регістр EAX відповідно до стандартних 64-бітових умов виклику c .
Через двійкове кодування, що доповнює два, це працює як для -ve, так і для + ve чисел.
Крім того, незважаючи на документацію, що говорить: "Якщо вміст вихідного операнда дорівнює 0, вміст операнда призначення не визначений". , Я знаходжу в моєму Ubuntu VM, що вихід 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, тому, як правило, я використовую компілятор, щоб генерувати для мене збірний код. Я з досвіду знаю, що розширення gcc, такі як __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
демонтаж:
$ 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
.