コンテンツにスキップ

機械語

出典: フリー教科書『ウィキブックス(Wikibooks)』

機械語(きかいご、machine language)とは、コンピュータの中央処理装置(CPU)が直接解釈して実行できる命令の集合のことを指します。これは、コンピュータの最も基本的なプログラム言語であり、0と1のビット列(バイナリ形式)で表されます。

主な特徴

[編集]
  1. CPU固有
    • 機械語はCPUのアーキテクチャに依存しており、異なるCPU(例: x86、ARM)では異なる機械語が用いられます。
  2. 直接実行可能
    • 機械語は、中間的な翻訳や解釈を必要とせず、CPUがそのまま実行します。
  3. 低水準言語
    • 高水準言語(例: C、Python)とは異なり、機械語はハードウェアに近い抽象度の低い言語です。プログラミングには非常に細かい作業が必要です。
  4. 命令セットアーキテクチャ(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
  • 解説: 引数nr0に格納されている)をレジスタr4に移動します。このr4が以降で引数nとして使われます。
*:      c: e3a05000      mov     r5, #0
  • 命令: mov r5, #0
  • 解説: レジスタr50を設定します。r5はこの後の計算結果の累積値を保持します。
*:     10: e3540002      cmp     r4, #2
  • 命令: cmp r4, #2
  • 解説: n < 2を判定するため、r42を比較しています。
*:     14: 30840005      addlo   r0, r4, r5
*:     18: 38bd8830      poplo   {r4, r5, r11, pc}
  • 命令: addlo r0, r4, r5
    • 解説: n01のとき、つまりn < 2の場合、r0r4r5の和を返します[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}: レジスタ r4r5r7lr (リンクレジスタ) をスタックに保存し、再帰的な呼び出しでもレジスタの値を保持します。
*:      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(スタックポインタ)を更新しています。また、x20x19 の値もスタックに保存しています。これにより、関数が呼び出されるたびにスタック上に新しいフレームが作成され、レジスタの状態が保持されます。
  • 初期化mov w19, wzr によって、w19 にゼロ (wzrはゼロレジスタ) を設定しています。これは n = 0 または n = 1 の場合の初期戻り値として使用されます。
10: 71000814      subs    w20, w0, #0x2
14: 540000e3      b.lo    0x30 <fibo+0x30>
  • 条件チェック:引数 nw0に格納)を使って 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>
  • 再帰呼び出しw0n-1 として設定して fibo(n-1) を呼び出します。この結果が w0 に返され、mov w8, w0 で一時保存します。その後 w0n-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)を返します。スタック上に保存していた x20x19x29、および 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 %rbpmovq %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 によって %ebxn の値)と定数 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 によって、%edin - 1 を設定し、fibo(n-1) を呼び出します。
    • callq 命令の後で %eax に返り値が格納されます。この %eax の値(fibo(n-1) の結果)を %r14d に加算して、合計を蓄積します。
    • 次に、%ebxn の値)を 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 %ebpmovl %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 によって、%eaxn - 1 を設定し、次の再帰呼び出しの引数とします。
    • pushl %eax で引数をスタックにプッシュし、calll によって fibo(n-1) を再帰呼び出しします。
    • 呼び出しが終わったら %eax に戻り値が格納されます。これを %edi に加算して、合計を蓄積します。
    • addl $-0x2, %esi により %esin - 2 に更新し、jmp 0xan - 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
  • 戻り値の設定と終了処理:最終的に %edifibo(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) - 複数のプロセッサを使用して、プログラムを並列に処理する方式。

脚註

[編集]
  1. ^ ARMアーキテクチャでは、多くの命令でキャリーなどのコンディションコードによって実行する・しないを制御できるのが大きな特徴で、他のアーキテクチャではジャンプ命令以外でコンディションコードによって実行する・しないを制御できるのは稀です(他にはIA-64がプレディケート可能です)。