x86_64マシンコード、4バイト
BSF(ビットスキャンフォワード)命令はまさにこれを行います!
0x0f 0xbc 0xc7 0xc3
gccスタイルのアセンブリでは、これは次のとおりです。
.globl f
f:
bsfl %edi, %eax
ret
入力はEDIレジスタで指定され、標準の64ビットc呼び出し規約に従ってEAXレジスタで返されます。
2の補数のバイナリエンコーディングのため、これは-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は、この回答がどのように導き出されたのかについての詳細を求めました。
私はx86アセンブリの複雑さよりもcに精通しているため、通常はコンパイラを使用してアセンブリコードを生成します。私は経験から、などのgcc拡張機能__builtin_ffs()
__builtin_ctz()
__builtin_popcount()
を知っており、通常はx86で1つまたは2つの命令にコンパイルおよびアセンブルします。だから私は次のような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
命令のマシンコードはis 0f bc c7
とforでret
あることがわかりc3
ます。