機械語
機械語(きかいご、machine language)とは、コンピュータの中央処理装置(CPU)が直接解釈して実行できる命令の集合のことを指します。これは、コンピュータの最も基本的なプログラム言語であり、0と1のビット列(バイナリ形式)で表されます。
主な特徴
[編集]- CPU固有
- 機械語はCPUのアーキテクチャに依存しており、異なるCPU(例: x86、ARM)では異なる機械語が用いられます。
- 直接実行可能
- 機械語は、中間的な翻訳や解釈を必要とせず、CPUがそのまま実行します。
- 低水準言語
- 高水準言語(例: C、Python)とは異なり、機械語はハードウェアに近い抽象度の低い言語です。プログラミングには非常に細かい作業が必要です。
- 命令セットアーキテクチャ(ISA)に基づく
- 機械語は、特定のCPUがサポートする命令セットアーキテクチャ(Instruction Set Architecture)に準拠しています。例えば、「データをロードする」「加算する」「分岐する」などの命令が含まれます。
機械語の例(x86-64アーキテクチャの場合)
[編集]以下は、add eax, 1
(EAXレジスタの値に1を加算する)の機械語表現です:
- バイナリ:
00000001 11000000
- 16進数:
01 C0
機械語と関連する他の言語
[編集]- アセンブリ言語
- 機械語に対して、人間が理解しやすい表現(ニーモニック)を使った低水準言語。アセンブリ言語をアセンブラで翻訳すると機械語になります。
- 例:
mov eax, 1
- 例:
- 機械語に対して、人間が理解しやすい表現(ニーモニック)を使った低水準言語。アセンブリ言語をアセンブラで翻訳すると機械語になります。
- 高水準言語
- 機械語やアセンブリ言語よりも人間にとって理解しやすい言語。コンパイラやインタプリタによって、最終的に機械語に変換されます。
- 例:
x = x + 1
(C言語の場合)
- 例:
- 機械語やアセンブリ言語よりも人間にとって理解しやすい言語。コンパイラやインタプリタによって、最終的に機械語に変換されます。
機械語の利点と欠点
[編集]- 利点
-
- 高速な実行: 中間翻訳が不要なため、直接CPUで実行できます。
- 細かい制御: ハードウェアの動作を詳細に制御できます。
- 欠点
-
- 難解さ: 人間には理解しにくく、プログラミングが非常に難しい。
- 非移植性: CPU固有のため、異なるCPU間で互換性がありません。
機械語の使用例
[編集]現代では、プログラマーが直接機械語を書くことはほとんどありませんが、次のような場面で関わります:
- 組み込みシステムの最適化
- OSのカーネルやドライバの設計
- セキュリティ関連の研究(例: バイナリ解析やリバースエンジニアリング)
以上が、機械語の基本的な説明です。興味があれば、具体的なCPUアーキテクチャ(例: x86-64、ARM)の命令セットを学ぶと、より深く理解できます。
ソースコードからどんな機械語が生成されるか
[編集]- フィボナッチ数を返す関数(C言語)
int fibo(int n) { *: if (n == 0 || n == 1) *: return n; *: return fibo(n-1) + fibo(n-2); }
32bitARMプロセッサーの例
[編集]- コンパイラーによって生成されたコード
$ clang-19 --target=arm -mfloat-abi=soft -c -Oz -g fibo.c $ llvm-objdump-19 --triple=arm -S fibo.o fibo.o: file format elf32-littlearm Disassembly of section .text: 00000000 <fibo>: ; int fibo(int n) { *: 0: e92d4830 push {r4, r5, r11, lr} *: 4: e28db008 add r11, sp, #8 *: 8: e1a04000 mov r4, r0 *: c: e3a05000 mov r5, #0 ; if (n == 0 || n == 1) *: 10: e3540002 cmp r4, #2 ; } *: 14: 30840005 addlo r0, r4, r5 *: 18: 38bd8830 poplo {r4, r5, r11, pc} ; return fibo(n-1) + fibo(n-2); *: 1c: e2440001 sub r0, r4, #1 *: 20: ebfffffe bl 0x20 <fibo+0x20> @ imm = #-0x8 *: 24: e0805005 add r5, r0, r5 *: 28: e2444002 sub r4, r4, #2 *: 2c: eafffff7 b 0x10 <fibo+0x10> @ imm = #-0x24
このコードは32bitARMプロセッサをターゲットとしたもので、すべての命令が32ビット長なのでアセンブラーの初学者向きです。 また、ARMアーキテクチャは多くのスマートフォンやタブレットで採用されていたり、組込み用途での採用も多いので最も普及しているコンピュータ・アーキテクチャの1つです。
各命令の説明
[編集]出力されたアセンブリの各行を詳細に見ていきます。
*: 0: e92d4830 push {r4, r5, r11, lr}
- 命令:
push {r4, r5, r11, lr}
- 解説: 関数のエントリポイントです。レジスタ
r4
,r5
,r11
,lr
(リンクレジスタ)をスタックに保存して、lr
は関数の復帰先アドレスを保持します。この命令によりスタックフレームが作成され、レジスタの内容が保存されます。
- 命令:
*: 4: e28db008 add r11, sp, #8
- 命令:
add r11, sp, #8
- 解説: フレームポインタ
r11
を現在のスタックポインタにオフセットを追加した値に設定し、スタックフレームの開始位置を決めます。
- 命令:
*: 8: e1a04000 mov r4, r0
- 命令:
mov r4, r0
- 解説: 引数
n
(r0
に格納されている)をレジスタr4
に移動します。このr4
が以降で引数n
として使われます。
- 命令:
*: c: e3a05000 mov r5, #0
- 命令:
mov r5, #0
- 解説: レジスタ
r5
に0
を設定します。r5
はこの後の計算結果の累積値を保持します。
- 命令:
*: 10: e3540002 cmp r4, #2
- 命令:
cmp r4, #2
- 解説:
n < 2
を判定するため、r4
と2
を比較しています。
- 命令:
*: 14: 30840005 addlo r0, r4, r5 *: 18: 38bd8830 poplo {r4, r5, r11, pc}
- 命令:
addlo r0, r4, r5
- 解説:
n
が0
か1
のとき、つまりn < 2
の場合、r0
にr4
とr5
の和を返します[1]。
- 解説:
- 命令:
poplo {r4, r5, r11, pc}
- 解説: 条件が成立(
n < 2
)した場合、関数から復帰します。スタックからレジスタr4
,r5
,r11
,pc
(プログラムカウンタ)を復元し、pc
に復帰アドレスが入ることで呼び出し元に戻ります。
- 解説: 条件が成立(
- 命令:
*: 1c: e2440001 sub r0, r4, #1 *: 20: ebfffffe bl 0x20 <fibo+0x20>
- 命令:
sub r0, r4, #1
- 解説:
n-1
を計算し、再帰呼び出しのためにr0
にセットします。
- 解説:
- 命令:
bl 0x20 <fibo+0x20>
- 解説:
fibo(n-1)
を再帰的に呼び出します。このとき、リンクレジスタlr
に次の命令アドレスが保存されます。
- 解説:
- 命令:
*: 24: e0805005 add r5, r0, r5
- 命令:
add r5, r0, r5
- 解説:
fibo(n-1)
の戻り値がr0
に格納されているため、それを累積値r5
に加算します。
- 命令:
*: 28: e2444002 sub r4, r4, #2
- 命令:
sub r4, r4, #2
- 解説:
n-2
を計算し、次の再帰呼び出しのためにr4
にセットします。
- 命令:
*: 2c: eafffff7 b 0x10 <fibo+0x10>
- 命令:
b 0x10 <fibo+0x10>
- 解説: ラベル
0x10
(再帰処理の比較部分)に無条件分岐します。
- 命令:
Thumb命令の例
[編集]ARMプロセッサはThumbと呼ばれるコード効率の向上を意図した16ビット長のThumb命令モードを持っています。
- コンパイラーによって生成されたコード
$ clang-19 --target=thumb -mfloat-abi=soft -mthumb -c -Oz -g fibo.c $ llvm-objdump-19 --triple=thumb -S fibo.o fibo.o: file format elf32-littlearm Disassembly of section .text: 00000000 <fibo>: ; int fibo(int n) { *: 0: b5b0 push {r4, r5, r7, lr} *: 2: af02 add r7, sp, #0x8 *: 4: 0004 movs r4, r0 *: 6: 2500 movs r5, #0x0 ; if (n == 0 || n == 1) *: 8: 2c02 cmp r4, #0x2 *: a: d305 blo 0x18 <fibo+0x18> @ imm = #0xa ; return fibo(n-1) + fibo(n-2); *: c: 1e60 subs r0, r4, #0x1 *: e: f7ff fffe bl 0xe <fibo+0xe> @ imm = #-0x4 *: 12: 1945 adds r5, r0, r5 *: 14: 1ea4 subs r4, r4, #0x2 *: 16: e7f7 b 0x8 <fibo+0x8> @ imm = #-0x12 ; } *: 18: 1960 adds r0, r4, r5 *: 1a: bdb0 pop {r4, r5, r7, pc}
各命令の説明
[編集]出力されたアセンブリの各行を詳細に見ていきます。
00000000 <fibo>: ; int fibo(int n) { *: 0: b5b0 push {r4, r5, r7, lr}
- 命令
push {r4, r5, r7, lr}
: レジスタr4
、r5
、r7
、lr
(リンクレジスタ) をスタックに保存し、再帰的な呼び出しでもレジスタの値を保持します。
- 命令
*: 2: af02 add r7, sp, #0x8
- 命令
add r7, sp, #0x8
: フレームポインタレジスタr7
をスタックポインタから8バイト分ずらして設定します。ローカル変数や引数へのアクセスを簡単にするために使用されます。
- 命令
*: 4: 0004 movs r4, r0 *: 6: 2500 movs r5, #0x0
- 命令
movs r4, r0
: 引数n
をレジスタr4
に保存します。 - 命令
movs r5, #0x0
: レジスタr5
に0を設定します。これはフィボナッチ計算の一部で、計算結果を保存していくためのレジスタです。
- 命令
; if (n == 0 || n == 1) *: 8: 2c02 cmp r4, #0x2 *: a: d305 blo 0x18 <fibo+0x18>
- 命令
cmp r4, #0x2
:n
の値 (r4
) を2と比較します。 - 命令
blo 0x18 <fibo+0x18>
:n
が2未満(すなわち0か1)なら、条件分岐して0x18番地にジャンプします。ここで関数の終了処理に進みます。
- 命令
; return fibo(n-1) + fibo(n-2); *: c: 1e60 subs r0, r4, #0x1 *: e: f7ff fffe bl 0xe <fibo+0xe> *: 12: 1945 adds r5, r0, r5 *: 14: 1ea4 subs r4, r4, #0x2 *: 16: e7f7 b 0x8 <fibo+0x8>
- 命令
subs r0, r4, #0x1
:r4
から1を引き、fibo(n-1)
を計算するための引数を設定します。 - 命令
bl 0xe <fibo+0xe>
:fibo
関数を再帰的に呼び出します。この再帰呼び出しはfibo(n-1)
を求めます。 - 命令
adds r5, r0, r5
: 呼び出しの結果がr0
に格納され、これをr5
に加えます。 - 命令
subs r4, r4, #0x2
:n
の値から2を引き、fibo(n-2)
を求める準備をします。 - 命令
b 0x8 <fibo+0x8>
: 再度fibo
関数に戻ってループを繰り返し、fibo(n-1) + fibo(n-2)
を計算します。
- 命令
*: 18: 1960 adds r0, r4, r5 *: 1a: bdb0 pop {r4, r5, r7, pc}
- 命令
adds r0, r4, r5
: 最終的な結果をr0
に格納し、戻り値として設定します。 - 命令
pop {r4, r5, r7, pc}
: スタックからレジスタの内容を復元し、関数の実行を終了します。
- 命令
Thumb命令は概ね命令長は16ビットで、長いオペランドが必要な命令(この場合は bl)だけが追加のオペランドを持ちます。
64bitARMプロセッサーの例
[編集]ARMアーキテクチャーは、ARMv8-Aから64ビットモードアーキテクチャーAArch64を採用してます。 AArch64は、32本の64ビットレジスター(うち1本はスタックポインター兼ゼロレジスタ、1本は戻り番地を保持するリンクレジスタ)を持ち、Xnnレジスターは64ビットレジスターのnn本目、WnnはXnnレジスターの下位32ビットです。
- コンパイラーによって生成されたコード
$ clang-19 --target=aarch64 -c -Oz -g fibo.c $ llvm-objdump-19 --triple=aarch64 -S fibo.o fibo.o: file format elf64-littleaarch64 Disassembly of section .text: 0000000000000000 <fibo>: ; int fibo(int n) { *: 0: a9be7bfd stp x29, x30, [sp, #-0x20]! *: 4: a9014ff4 stp x20, x19, [sp, #0x10] *: 8: 910003fd mov x29, sp *: c: 2a1f03f3 mov w19, wzr ; if (n == 0 || n == 1) *: 10: 71000814 subs w20, w0, #0x2 *: 14: 540000e3 b.lo 0x30 <fibo+0x30> ; return fibo(n-1) + fibo(n-2); *: 18: 51000400 sub w0, w0, #0x1 *: 1c: 94000000 bl 0x1c <fibo+0x1c> *: 20: 2a0003e8 mov w8, w0 *: 24: 2a1403e0 mov w0, w20 *: 28: 0b130113 add w19, w8, w19 *: 2c: 17fffff9 b 0x10 <fibo+0x10> ; } *: 30: 0b130000 add w0, w0, w19 *: 34: a9414ff4 ldp x20, x19, [sp, #0x10] *: 38: a8c27bfd ldp x29, x30, [sp], #0x20 *: 3c: d65f03c0 ret
このコードは、AArch64アーキテクチャ用にコンパイルされた再帰的なフィボナッチ関数 fibo
のアセンブリリストです。以下で各部分を解説します。
0: a9be7bfd stp x29, x30, [sp, #-0x20]! 4: a9014ff4 stp x20, x19, [sp, #0x10] 8: 910003fd mov x29, sp c: 2a1f03f3 mov w19, wzr
- スタックフレームの設定:
stp
命令でリンクレジスタ (x30
) とフレームポインタ (x29
) をスタックに保存し、sp
(スタックポインタ)を更新しています。また、x20
とx19
の値もスタックに保存しています。これにより、関数が呼び出されるたびにスタック上に新しいフレームが作成され、レジスタの状態が保持されます。 - 初期化:
mov w19, wzr
によって、w19
にゼロ (wzr
はゼロレジスタ) を設定しています。これはn = 0
またはn = 1
の場合の初期戻り値として使用されます。
- スタックフレームの設定:
10: 71000814 subs w20, w0, #0x2 14: 540000e3 b.lo 0x30 <fibo+0x30>
- 条件チェック:引数
n
(w0
に格納)を使ってn < 2
かどうかをチェックしています。subs w20, w0, #0x2
によって、n - 2
の結果がw20
に格納され、b.lo
命令でn < 2
の場合に0x30
のラベルにジャンプします。これによりn
が 0 または 1 の場合は直接n
を返す処理に移行します。
- 条件チェック:引数
18: 51000400 sub w0, w0, #0x1 1c: 94000000 bl 0x1c <fibo+0x1c> 20: 2a0003e8 mov w8, w0 24: 2a1403e0 mov w0, w20 28: 0b130113 add w19, w8, w19 2c: 17fffff9 b 0x10 <fibo+0x10>
- 再帰呼び出し:
w0
をn-1
として設定してfibo(n-1)
を呼び出します。この結果がw0
に返され、mov w8, w0
で一時保存します。その後w0
にn-2
を設定し、再度fibo(n-2)
を呼び出し、その結果をw19
に加算します。b 0x10
によってループに戻り、これを繰り返してfibo
を計算します。
- 再帰呼び出し:
30: 0b130000 add w0, w0, w19 34: a9414ff4 ldp x20, x19, [sp, #0x10] 38: a8c27bfd ldp x29, x30, [sp], #0x20 3c: d65f03c0 ret
- 結果を返す:計算が完了したら、結果(
w0
)を返します。スタック上に保存していたx20
、x19
、x29
、およびx30
を復元し、ret
で関数から戻ります。
- 結果を返す:計算が完了したら、結果(
wzrはゼロレジスターを32bitで参照しています、spはスタックポインターでアドレス演算の文脈と左辺値の文脈ではスタックポインター、右辺値の場合はゼロレジスターになりレジスタインデックス(31番)を共有しています。 aarch64 では32bitARMと違って全ての命令に条件フラッグ参照が着くわけではないので、どちらかというと Thumb に似ていますが最小命令サイズは32bitです。
amd64プロセッサーの例
[編集]amd64(X86-64とも)の命令は最小単位は1バイトで、バイト数あたりの操作が多いのが特徴です。 逆アッセンブルされたコードを読む限り不便は感じませんが、ハンドディスアッセンブルする場合は1バイトずれるとまるで違った意味になるのが厄介で、プロセッサーの中でも数命令先の命令を読み込み実行効率を上げる為に命令の切れ目を探すことが性能向上のボトルネックになっています(ARMなら次の命令は4バイト先と決まっているので深い先読みが相対的に容易)。
- 同じコードをamd64向けにコンパイル
$ clang-19 --target=amd64 -c -Oz -g fibo.c $ llvm-objdump-19 --triple=amd64 -S fibo.o fibo.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <fibo>: ; int fibo(int n) { *: 0: 55 pushq %rbp *: 1: 48 89 e5 movq %rsp, %rbp *: 4: 41 56 pushq %r14 *: 6: 53 pushq %rbx *: 7: 89 fb movl %edi, %ebx *: 9: 45 31 f6 xorl %r14d, %r14d ; if (n == 0 || n == 1) *: c: 83 fb 02 cmpl $0x2, %ebx *: f: 72 10 jb 0x21 <fibo+0x21> ; return fibo(n-1) + fibo(n-2); *: 11: 8d 7b ff leal -0x1(%rbx), %edi *: 14: e8 00 00 00 00 callq 0x19 <fibo+0x19> *: 19: 41 01 c6 addl %eax, %r14d *: 1c: 83 c3 fe addl $-0x2, %ebx *: 1f: eb eb jmp 0xc <fibo+0xc> ; } *: 21: 44 01 f3 addl %r14d, %ebx *: 24: 89 d8 movl %ebx, %eax *: 26: 5b popq %rbx *: 27: 41 5e popq %r14 *: 29: 5d popq %rbp *: 2a: c3 retq
このアセンブリコードは、x86-64 アーキテクチャ用にコンパイルされた再帰的なフィボナッチ関数 fibo
の内容を示しています。各命令が何をしているか解説します。
0: 55 pushq %rbp 1: 48 89 e5 movq %rsp, %rbp 4: 41 56 pushq %r14 6: 53 pushq %rbx 7: 89 fb movl %edi, %ebx 9: 45 31 f6 xorl %r14d, %r14d
- スタックフレームの設定:
pushq %rbp
とmovq %rsp, %rbp
によってスタックフレームを設定しています。また、関数内で使用する%r14
と%rbx
レジスタをスタックに退避させ、関数終了時に復元できるようにしています。 - 引数の格納と初期化:引数
n
は%edi
レジスタに格納されているため、これを%ebx
に移しています(movl %edi, %ebx
)。また、%r14d
をゼロクリアして初期化しています。この%r14
は、最終的な戻り値のための一時的な蓄積レジスタとして使用されます。
- スタックフレームの設定:
c: 83 fb 02 cmpl $0x2, %ebx f: 72 10 jb 0x21 <fibo+0x21>
- 条件チェック:引数
n
の値が 2 未満 (n < 2
) かどうかを比較しています。cmpl $0x2, %ebx
によって%ebx
(n
の値)と定数2
を比較し、jb
(jump if below)命令によってn < 2
の場合は、アドレス0x21
にジャンプします。これは、フィボナッチ数列の初期条件に当たるケースです。
- 条件チェック:引数
11: 8d 7b ff leal -0x1(%rbx), %edi 14: e8 00 00 00 00 callq 0x19 <fibo+0x19> 19: 41 01 c6 addl %eax, %r14d 1c: 83 c3 fe addl $-0x2, %ebx 1f: eb eb jmp 0xc <fibo+0xc>
- 再帰呼び出し:
leal -0x1(%rbx), %edi
によって、%edi
にn - 1
を設定し、fibo(n-1)
を呼び出します。callq
命令の後で%eax
に返り値が格納されます。この%eax
の値(fibo(n-1)
の結果)を%r14d
に加算して、合計を蓄積します。- 次に、
%ebx
(n
の値)をn - 2
に更新し、再びjmp 0xc
によってn - 2
での再帰を行い、計算が完了するまで繰り返します。
- 再帰呼び出し:
21: 44 01 f3 addl %r14d, %ebx 24: 89 d8 movl %ebx, %eax 26: 5b popq %rbx 27: 41 5e popq %r14 29: 5d popq %rbp 2a: c3 retq
- 戻り値の設定と終了処理:ここで蓄積していた
%r14d
を%ebx
に加算し、最終的な戻り値を%eax
に格納します。そして、保存しておいたレジスタ%rbx
、%r14
、%rbp
を復元し、retq
命令で関数から戻ります。
- 戻り値の設定と終了処理:ここで蓄積していた
このコードは再帰的なフィボナッチ関数を最適化していますが、各再帰呼び出しの後に部分的な結果を加算していくため、再帰が深くなるとスタックの使用量が増えます。このコードは、コンパイラの最適化オプション -Oz
によりコードサイズが最小化されています。
x86(32ビット)のコード
[編集]- x86(32ビット)のコード
$ clang-19 --target=i686 -c -Oz -g fibo.c $ llvm-objdump-19 --triple=i686 -S fibo.o fibo.o: file format elf32-i386 Disassembly of section .text: 00000000 <fibo>: ; int fibo(int n) { *: 0: 55 pushl %ebp *: 1: 89 e5 movl %esp, %ebp *: 3: 57 pushl %edi *: 4: 56 pushl %esi *: 5: 31 ff xorl %edi, %edi *: 7: 8b 75 08 movl 0x8(%ebp), %esi ; if (n == 0 || n == 1) *: a: 83 fe 02 cmpl $0x2, %esi *: d: 72 11 jb 0x20 <fibo+0x20> ; return fibo(n-1) + fibo(n-2); *: f: 8d 46 ff leal -0x1(%esi), %eax *: 12: 50 pushl %eax *: 13: e8 fc ff ff ff calll 0x14 <fibo+0x14> *: 18: 59 popl %ecx *: 19: 01 c7 addl %eax, %edi *: 1b: 83 c6 fe addl $-0x2, %esi *: 1e: eb ea jmp 0xa <fibo+0xa> ; } *: 20: 01 fe addl %edi, %esi *: 22: 89 f0 movl %esi, %eax *: 24: 5e popl %esi *: 25: 5f popl %edi *: 26: 5d popl %ebp *: 27: c3 retl
このアセンブリコードは、i386(32ビット)アーキテクチャ用にコンパイルされた再帰的なフィボナッチ関数 fibo
の内容を示しています。各命令が何をしているかを解説します。
0: 55 pushl %ebp 1: 89 e5 movl %esp, %ebp 3: 57 pushl %edi 4: 56 pushl %esi 5: 31 ff xorl %edi, %edi 7: 8b 75 08 movl 0x8(%ebp), %esi
- スタックフレームの設定:
pushl %ebp
とmovl %esp, %ebp
で新しいスタックフレームを作成します。また、関数内で使用するレジスタ%edi
と%esi
をスタックに保存して、関数終了時に復元できるようにしています。 - 初期化:
xorl %edi, %edi
によって%edi
をゼロクリアしています。このレジスタは結果の一時的な蓄積に使われます。 - 引数の取得:引数
n
はスタックから取り出して%esi
に格納しています(movl 0x8(%ebp), %esi
)。これは、fibo
関数の引数n
です。
- スタックフレームの設定:
a: 83 fe 02 cmpl $0x2, %esi d: 72 11 jb 0x20 <fibo+0x20>
- 条件チェック:
cmpl $0x2, %esi
で%esi
(引数n
)と2
を比較し、jb
(jump if below)命令によってn < 2
の場合にアドレス0x20
にジャンプします。これは、フィボナッチ数列の初期条件をチェックする部分です。
- 条件チェック:
f: 8d 46 ff leal -0x1(%esi), %eax 12: 50 pushl %eax 13: e8 fc ff ff ff calll 0x14 <fibo+0x14> 18: 59 popl %ecx 19: 01 c7 addl %eax, %edi 1b: 83 c6 fe addl $-0x2, %esi 1e: eb ea jmp 0xa <fibo+0xa>
- 再帰呼び出し:
leal -0x1(%esi), %eax
によって、%eax
にn - 1
を設定し、次の再帰呼び出しの引数とします。pushl %eax
で引数をスタックにプッシュし、calll
によってfibo(n-1)
を再帰呼び出しします。- 呼び出しが終わったら
%eax
に戻り値が格納されます。これを%edi
に加算して、合計を蓄積します。 addl $-0x2, %esi
により%esi
をn - 2
に更新し、jmp 0xa
でn - 2
の場合の計算を再び行います。
- 再帰呼び出し:
20: 01 fe addl %edi, %esi 22: 89 f0 movl %esi, %eax 24: 5e popl %esi 25: 5f popl %edi 26: 5d popl %ebp 27: c3 retl
- 戻り値の設定と終了処理:最終的に
%edi
(fibo(n-1)
の結果の蓄積)が%esi
に加算され、戻り値として%eax
に格納されます。スタックから%esi
、%edi
、%ebp
を復元し、retl
によって関数から戻ります。
- 戻り値の設定と終了処理:最終的に
このコードも再帰的なフィボナッチ関数を実装しています。再帰呼び出しでの部分的な計算結果が %edi
に蓄積され、スタックの使用が少ないように最適化されています。
まとめ
[編集]- 異なるプロセッサーでは、同じ高級言語のコードが全く違う機械語命令列にコンパイルされる。
- 同じプロセッサーでも命令モードによって、同じ高級言語のコードが全く違う機械語命令列にコンパイルされる。
- 再帰などのプログラム構造もコンパイラーによって(意味解析され)等価のより速度的に(あるいはメモリーフットプリント的に)優れたコードに置き換えられる(事がある)。
例えば、画像データや映像データはバイナリーデータですが機械語ではありません。
実行ファイルもバイナリーデータですが、オペレーションシステムが「どの様に配置するのか?」「どの位置から実行するのか?」あるいは「初期化済みのデータ領域の値」など機械語以外の付帯的な情報(一般にヘッダーと呼ばれます)を重層的に持っているので、機械語を含んでいますが機械語そのものではありません。
機械語は、アセンブラのニーモニックに一対一で対応するコードと理解するとわかりやすいと思います。用語集
[編集]機械語に関する用語集
- オペコード(Opcode) - 機械語命令の操作コード。特定の動作を実行するための識別子。
- オペランド(Operand) - オペコードによって指定された操作対象。演算の対象となる数値やアドレス。
- レジスタ(Register) - CPU内にある高速なメモリ領域で、演算に使用されるデータやアドレスを格納するために使用される。
- フラグ(Flag) - CPUの状態を示すビットで、演算の結果を表す。フラグは、CPU内部で条件分岐を行うために使用される。
- メモリアドレス(Memory Address) - メモリ内の特定の場所を示す番号。機械語のオペランドとして使用されることが多い。
- メモリマップドI/O(Memory-mapped I/O) - I/Oデバイスを制御するために、メモリアドレスを使用する方法。
- ファイルI/O(File I/O) - ディスクやネットワーク上のファイルを操作するために使用される命令。
- 命令ポインタ(Instruction Pointer) - CPUが次に実行する機械語命令のアドレスを示すレジスタ。
- サブルーチン(Subroutine) - 他の部分から呼び出される、独立した機械語のブロック。
- スタック(Stack) - プログラム内で一時的なデータを格納するためのメモリ領域。
- エンディアン(Endian) - データの並び順を示す方法。リトルエンディアンは、最下位バイトから順にデータを配置する方式。ビッグエンディアンは、最上位バイトから順にデータを配置する方式。
- マシンサイクル(Machine Cycle) - CPUが一つの命令を実行するために必要なサイクル数。
- 命令セット(Instruction Set) - 特定のCPUが実行できる機械語命令の集合。
- アセンブラ(Assembler) - アセンブリ言語を機械語に変換するプログラム。
- リンカ(Linker) - 複数のオブジェクトファイルを結合して、実行可能なプログラムを生成するプログラム。
- デバッガ(Debugger) - プログラムの実行中に、機械語命令やレジスタの値を監視し、プログラムの動作を解析するツール。
- トレース(Trace) - プログラムの実行中に、実行された命令やメモリアクセスなどの履歴を保存すること。
- ブレークポイント(Breakpoint) - プログラムの実行中に、特定の命令の実行を一時停止し、デバッグのために命令の内容やレジスタの値を確認するために使用されるポイント。
- クロスコンパイラ(Cross-compiler) - 特定のCPU向けに、異なるプラットフォームでコンパイルするためのコンパイラ。
- リバースエンジニアリング(Reverse Engineering) - プログラムやハードウェアの動作を解析して、仕様や設計図を作成するプロセス。
- コンパイル(Compile) - 高水準言語で書かれたプログラムを、機械語に変換するプロセス。
- リンク(Link) - コンパイルされた複数のオブジェクトファイルを、実行可能なプログラムに結合するプロセス。
- アセンブル(Assemble) - アセンブリ言語で書かれたプログラムを、機械語に変換するプロセス。
- ローダ(Loader) - プログラムをメモリに読み込み、実行可能な状態にするプログラム。
- バイトコード(Bytecode) - 実行環境に依存しない、仮想マシン上で実行される機械語。
- オーバーフロー(Overflow) - データ型の最大値を超える演算が行われた場合に発生する、予期しない結果のこと。
- セグメンテーション違反(Segmentation Fault) - プログラムがメモリの範囲外をアクセスしようとした場合に発生するエラー。
- プロセス(Process) - プログラムの実行中に割り当てられる、メモリやレジスタ、実行状態などのリソースの集合。
- マルチプロセッシング(Multiprocessing) - 複数のプロセッサを使用して、プログラムを並列に処理する方式。
脚註
[編集]- ^ ARMアーキテクチャでは、多くの命令でキャリーなどのコンディションコードによって実行する・しないを制御できるのが大きな特徴で、他のアーキテクチャではジャンプ命令以外でコンディションコードによって実行する・しないを制御できるのは稀です(他にはIA-64がプレディケート可能です)。