機械語(マシン語)とアセンブリ言語

機械語はコンピュータが理解できる唯一のプログラミング言語

コンピュータはプログラム中に記述された命令をCPUで解釈し実行しています。

プログラミング言語は現在様々なものが存在します(例:C, C++, C#, Java, PHP, Perl, Ruby, Lisp, Prolog, BASIC, etc)。

しかし、これらのプログラミング言語はあくまでも人間がプログラムを組むために作られた言語でコンピュータ(CPU)が直接理解できるわけではありません。

人間から見ると、通常のプログラミング言語もかなり機械寄りの表現に感じるかもしれませんが、機械の立場から見るとまだまだ人間寄りなのです。

CPUが直接理解できるプログラムは機械語(マシン語)と呼ばれる数字の並びで構成されたものだけです。

数字の並びは2進数の並びであり、結局、onとoffの並びであることは、論理回路で学習しました。

機械語以外のプログラミング言語で記述されたプログラムは、必ず機械語で記述されたプログラムに変換されてから実行されます。

よって、プログラミングの根本を知ろうとすると機械語(さらには、論理回路)に行き着くことになります。

機械語を学ぶことによって、プログラムを実行するときのコンピュータの動作とプログラミング言語をより深く理解することが出来るようになります。

アセンブリ言語

コンピュータは数字の並びである機械語しか理解できませんが、これを人間が書くのは分かりにくく大変です。

機械語を人間でも分かりやすくするために簡略化した英単語や記号を一対一に対応させたものをアセンブリ言語といいます。

他のプログラミング言語との違いは、機械語に一対一で対応していることです。つまりアセンブリ言語でプログラムを書くことは機械語を書くことに等しいといえます。

他のプログラミング言語は、一対一には対応せず、機械語を意識しなくてもプログラミングを行えるようになっているのが普通です。

機械語でプログラミングすることの理由

機械語はCPUの全ての機能を直接操作可能で、最も効率よくプログラムを記述できる可能性があります。

よって、デバイスドライバやOSの中心部など機械を直接操作したり効率が重要な部分はアセンブリ言語を使ってプログラムが記述されることが多いです。

プログラム全体をアセンブリ言語で記述することは、とても手間の掛かることなので、通常は比較的小規模なプログラムやプログラムの効率が優先される部分を記述します。

また、全てのプログラミング言語は機械語に変換されてから実行されますからプログラミングを深く理解するには機械語に触れておくことが重要です。

アセンブラ

アセンブリ言語で記述されたプログラムを機械語で記述されたプログラム(数字の並び)に変換するプログラムです。

学習内容・目標

2講時では機械語の全てを理解するのは不可能ですので、実際に操作しながら感覚をつかんでこれからより深く学習する必要に迫られたときのスムーズな導入の助けとなることを目標にします。 学習の目標をまとめると以下のようになります。

学習内容は以下の通りです。


C語のプログラム

C語はプログラミング言語の一つで、一般的なプログラムを作ることの出来る汎用プログラミング言語です。C語で記述された言語は、コンパイラによって機械語で記述されたプログラムに変換され実行可能になります。

この実習では、はじめにC++言語(C言語の拡張版)で簡単なプログラムを書き、そのプログラムがどのように機械語に変換されるのか理解します。

以降、Visual C++をVCと呼ぶときはVisual Studio 2008、Visual Studio 2005、 Visual Studio.NETのVisual C++をまとめて指します。また、各バージョンが重要な時はVC2008、VC2005、VC.NETなどと区別します。

サンプル1

int main()
{

int a;
int b;
int c;
b=1;
c=2;
a = b + c;
return 0;
}

上のプログラムは、整数型の変数a, b, cを作り変数bに1、変数cに2を代入して、変数aにb+cの計算結果を代入します。

C語では、プログラムは基本的に関数の集合として記述します。上のmain関数は特別な関数で必ずこの関数から実行が始まります。 上のような簡単なプログラムはmain関数の中に全ての記述を書けます。

関数の前のintはこの関数が返り値として整数を返すことを表します。これを型と呼びます。intはinteger(整数)のことです。

C語は型に厳しい言語で、変数や関数は必ず型を指定しなくてはなりません。

main関数の内容は{}に囲まれた部分に記述します。

main関数の最後の行のreturn文はmain関数が返す数字(ここでは0)を指定しています。この返り値はこのプログラムを呼び出したシステムが使います。0は正常終了を表します。

main関数の直後の括弧()は引数を取らない関数であることを表します。関数は一般的に引数を取りその内容に対して返す値が決まりますが、必要の無い場合はこのように単に括弧で閉じます。main関数に引数を取らせることもできますがここでは触れません。

サンプルの内容でこの学習にとって重要なのは、3つの整数型の変数a,b,cが定義され、その中の2つの変数b,cに値が代入されその和を計算し、そしてその結果を変数aに代入している ことを理解することです。

Visual Studio

Visual Studioとはマイクロソフトのプログラミング環境です。

学年によってインストールされているバージョンが違います。自分のパソコンにどのバージョンがインストールされているか確認してください。

現在学内にあるバージョンは古い順に、Visual C++6.0 (VC6), Visual Studio.NET2003 (VC.NET), Visual Studio 2005 (VC2005)、Visual Studio 2008(VC8)、Visual Studio 2010(VC10)です。

サンプル1から生成された機械語を読むための準備

次に上のプログラムを実際に入力してプログラムをコンパイルして機械語を生成します。

まず、スタートメニューからVisual Studioを起動します。

以降、VC8、VC2005、VC.NETとVC6で若干操作が異なるので、異なる部分は枠で囲い別々に説明します。枠で囲まれている文章は自分が使っているバージョンのほうを読んでください。

VC10,VC8,VC2005, VC.NETの場合

ファイル→新規作成→プロジェクトをクリックします。

 

VC6の場合

下の図のようにスタートメニューからたどってVisual C++ 6.0をクリックし起動します。

すると下図の用なウインドウが出てきます。

 

VC10,VC8,VC2005, VC.NETの場合

ダイアログボックスが出てきますので、Visual C++ プロジェクトを選択し、右側のテンプレートのアイコンからWin32コンソールプロジェクトを選択します。

VC.NET

VC2005

下側のプロジェクト名にsample1、場所は皆さんのパソコンのハードディスクの適切な場所を指定してください。この場所は忘れないようにしてください。通常はこの講義 用のフォルダを予め作りそこを指定すると良いでしょう。

するとウィザードが起動するのでアプリケーションの設定クリックして表示を切りた後、空のプロジェクトをチェックします。

VC.NET

VC2005

完了ボタンを押すと空のプロジェクトが出来ます。 (この例では、場所をZ\コンピュータアーキテクチャを指定するとその下にプロジェクト名と同じ\sample1というフォルダが自動で作成されその中に必要はファイルが格納されます。)

 

 

VC6の場合

あらかじめ、プログラムを保存するためのフォルダを作成してください。名前は自由にして構いませんが、ここの例では「C:\コンピュータアーキテクチャ」としています。

ファイル→新規作成をクリック。すると下図のようなウインドウが出てきます。

ここで、プロジェクト名、位置を設定し、Win32 Console Applicationをクリックします。そしてOKボタンをクリックします。

(この例では、位置を「C:\コンピュータアーキテクチャ」を指定しています。するとOKボタンをクリックした後指定した位置のフォルダの中にプロジェクト名と同じ\sample1というフォルダが自動で作成され、さらに、その中に必要はファイルが格納されます。)

空のプロジェクトのチェックがついていることを確認して「終了」ボタンをクリックします。

 

VC10,VC8,VC2005, VC.NETの場合

VC.NET

VC2005はソリューションエクスプローラが左になりました。

次に右側にあるソリューションエクスプローラの一番上のsample1を右クリックしてメニューを出し、「追加」→「新しい項目の追加」を選択します。出てきた ダイアログボックスのテンプレートからC++ファイル(.CPP)を選択し、下にあるファイル名main.cppとして開くボタンをクリックします。

VC.NET

VC2005。<名前を入力してください>という部分にmainと入力する。

すると空のmain.cppというテキストファイルが作られエディタに開かれます。

 

 

VC6の場合

プロジェクト→プロジェクトへ追加→新規作成をクリックします。

下の図のようにファイル名にmain.cppと入力し、上から2番目のC++ソースファイルをクリックして選択します。そしてOKボタンをクリックします。

すると、VC.NETと同様に空のテキストファイルが編集可能な状態で開かれます。

ファイルmain.cppに上のサンプル1のプログラムをそっくり入力してください。 空白の行はあっても無くても変わりません。ただし、単語の間の空白は意味がありますのでその通り入力してください。入力は基本的に半角です。大文字と小文字が区別されるので注意してください。このサンプルでは半角小文字(直接入力)で入力します。

メニューの「ビルド」→「ソリューションのビルド」でコンパイルが始まり機械語が生成されます。

生成されたアセンブリ言語と機械語を見る

次に機械語を見てみましょう。

このエディタは、実行時に実行されている部分の機械語(アセンブリ言語)を見ることが出来ます。通常人間に分かりやすいのはアセンブリ言語なのでデフォルトではアセンブリ言語だけが表示されます。

まず、ブレークポイントを設定します。これは、そのまま実行するとすぐ終了してしまうのでプログラムを途中で停止させるための操作です。

VC10,VC8,VC2005, VC.NETの場合

ブレークポイントの設定の仕方は簡単で、止めたいプログラムの行の左側のところをクリックして赤い丸をつけます。赤い丸をクリックすると消えてブレークポイントは解除されます。

ここでは、b=1のところにブレークポイントを設定します。

VC6の場合

b=1; の行を一回クリックしてカーソルをその行に移します。その後右クリックして下の図のようにメニューを出し、「ブレークポイントの挿入/削除」をクリックします。

赤い点が表示されたらブレークポイントはうまく設定されました。

 

次に実行します。

ファンクションキーのF5を押すか、メニューのデバッグ→実行をクリックします。 (実行を中止するときは,、SHIFT+F5を使うか、黒く四角いボタンをクリックします)

VC2005の場合

すると、表示が変わり、ブレークポイントの赤い丸に黄色い矢印が重なって実行が変数bに1を代入する直前で止まっていることを示します (F10で一ステップずつ実行でき、F5で続行できます)。

アセンブリ言語を表示するために以下の操作をします。

デバッグ→ウインドウ→混合モード(または逆アセンブル)をクリックします。

VC2005の場合

すると表示が変わって、C++のプログラムコードの間にアセンブリ言語が表示されました。

基本的に、C++のプログラムの記述の直後がそれに相当するアセンブリ言語の記述です。ただし、必ずしも一対一に対応してるわけではないので 直後に機械語が表示されていないところもあります。サンプル1では、最初の3行の変数の宣言(int a;以降)に相当する機械語は無いことが分かります。

アセンブリ言語の行は、一番左にメモリのアドレスが8桁の16進数で表示されます(16進数につては下でおさらいします)。次にその右側にアセンブリ言語が表示されます。これは、機械語がメモリに格納されているため、それに対応するアセンブリ言語も位置がメモリのアドレスで指定されるためです。

次に機械語を表示しましょう。逆アセンブリのウインドウで、右クリックして「コードバイトの表示」をクリックすると機械語が表示されます。

アドレスとアセンブリ言語の間に現れた16進数の並びが機械語です。

アセンブリ言語の各命令と、数字が対応しているのが分かります。CPUはこの数字をプログラムとして解釈し実行しています。

16進数と2進数

機械語を学習する上で2進数と16進数の簡単な知識は必要です。ここではおさらい程度に説明します。

2進数

コンピュータは電気で動くので、その動作の基本はONとOFFです。

例えば回路の電圧の高い状態をON、電圧がかかっていない状態をOFFと決めます。このようなスイッチに相当する回路(実際にはコンデンサです)を並べると複数の状態を表現できます。

仮に、ONの状態を1、OFFの状態を0で表すとします。スイッチが2つ横に並んでいるとすると

0 0: 2つともOFFの状態
0 1: 右側のスイッチだけONの状態
1 0: 左側のスイッチだけONの状態
1 1: 両方ともONの状態

のように4通りの状態を表すことが出来ます。

例えば、

0 0 の状態を 0、 0 1 の状態を 1、 1 0 の状態を 2、そして、1 1の状態を3と見ると約束すると0から3までの数字を2つのスイッチで表すことが出来ます。

また、例えば

0 0 の状態を 0、 0 1 の状態を 1、 1 1 の状態を -1、そして、1 0の状態を-2と見ると約束すると-2から1までの数字を2つのスイッチで表すことが出来ます。

どちらも4通りの数を表しています。

今度はスイッチを8個にするとどうなるでしょう。

0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 1
0 0 0 0 0 0 1 0
0 0 0 0 0 0 1 1
0 0 0 0 0 1 0 0
0 0 0 0 0 1 0 1
0 0 0 0 0 1 1 0
0 0 0 0 0 1 1 1
0 0 0 0 1 0 0 0
0 0 0 0 1 0 0 1
0 0 0 0 1 0 1 0
0 0 0 0 1 0 1 1
     (中略)
1 1 1 1 1 1 1 0
1 1 1 1 1 1 1 1

と256通りの数を表すことが出来ます。これも、例えば、

0 0 0 0 0 0 0 0 = 0
0 0 0 0 0 0 0 1 = 1
0 0 0 0 0 0 1 0 = 2
0 0 0 0 0 0 1 1 = 3
  (中略)
1 1 1 1 1 1 1 0 = 254
1 1 1 1 1 1 1 1 = 255

と対応付けると0から255までの256通りの整数を表現することが出来ます。

このスイッチのことをビットと呼びます。最初のスイッチの組み合わせでは、スイッチは2つなので2ビットで数を表しているといえます。8個の組み合わせは8ビットで数を表しているといえます。

最近のパソコンは32ビットコンピュータと呼ばれますが、これは基本的なデータ処理の単位が32ビットで構成されていることを表します。

そして、この0と1で構成される数字は2で一桁繰り上がっていることになるので2進数と呼びます。2進数の10は私たちが通常使う10進数の2になります。

コンピュータは基本的に2進数で全てを表現しています。

ただし、2進数は桁が大きくなりこれを使うと見づらいので表示するときは10進数や16進数を使います。特に、機械語では桁が揃う16進数をよく使います。

16進数

2進数は2で一桁繰り上がり、10進数では10で一桁繰り上がります。それと同様に16進数は16で一桁繰り上がります。逆に16進数では15までを一桁で表します。しかし、私たちが通常使う10進数では0〜9までの10通りしか一桁を表す数字の表現を持っていないので10をA、11をB、12をC、13をD,14をE、15をFで表す約束になっています。

よって

2進数               10進数  16進数
0 0 0 0 0 0 0 0 =  0       =  0
0 0 0 0 0 0 0 1 =  1       =  1
0 0 0 0 0 0 1 0 =  2       =  2
0 0 0 0 0 0 1 1 =  3       =  3
  (中略)
0 0 0 0 1 0 0 1 =  9       =  9
0 0 0 0 1 0 1 0 = 10      =  A
0 0 0 0 1 0 1 1 = 11      =  B
0 0 0 0 1 1 0 0 = 12      =  C
0 0 0 0 1 1 0 1 = 13      =  D
0 0 0 0 1 1 1 0 = 14      =  E
0 0 0 0 1 1 1 1 = 15      =  F
0 0 0 1 0 0 0 0 = 16      = 10
0 0 0 1 0 0 0 1 = 17      = 11
    (中略)
1 1 1 1 1 1 1 0 = 254    = FE
1 1 1 1 1 1 1 1 = 255    = FF

という対応関係を取ることができます。

このことを踏まえて、先ほどの画面をもう一度見てみます。

アドレスの部分は8桁の16進数になっています。16進数の一桁は2進数の4桁で表現できるのでアドレスは32ビットで表現されています。

アセンブリ言語の左側に最後にhがついている数がありますがこれは16進数です。ここには10進数や2進数も記述することができるため混同が無いように16進数の場合は数字の最後にhをつけます。何もつけないと10進数と解釈されます。 最後にbをつけると2進数を表します。

さて、やっとアセンブラ自体の学習に入る準備ができました。

再開の仕方

プロジェクトの新規作成のときに作られたフォルダの中にある拡張子が.slnのファイルをダブルクリックします。

次へ進む