アセンブリ言語・入門の前に（１）　

老システムエンジニアが「最近の若い者はアセンブリ言語も書けない」とボヤくので、「そりゃー必要がないからさ」と返しておきました。確かに仕事で使わない上に、参考書もほとんどなく、周りに仲間もいないのでは、現役プログラマーの方々が敬遠したくなるのも無理はありません。
アセンブリ言語はたいへん敷居が高く、取り付きにくい言語です。このメモは入門というより、読者が入門前に軽いウォーミングアップをしていただくために書きました。三回に分けて執筆し、今回の主役はキャリーフラグとゼロフラグです。

### １　私のプログラム第１号

パソコンの黎明期にＴＫ－８０という機械がありました。（一発で歳がバレる:sweat_smile:）サンプルとして１から１００まで加えるプログラムが付いていて、実行させるとノータイムで答えの13ba（十進数の５０５０）を表示し、感激したものです。あれを再現してみましょう。
Ｃ言語では次のようになりますね。
```c:my_first.c
/*** my first program (my_first.c) ***/

#include <stdio.h>

int main(int argc, char **argv)

{ unsigned short answer;
  unsigned short addend;
  unsigned short thelimit;

  answer = 0;
  addend = 1;
  thelimit = 100;

  do
  { answer += addend;
    addend++;
  } while ( addend <= thelimit );

  printf("%x\n", answer);

  return 0;
}
```


当時はアセンブラもコンパイラもなかったので、機械語の命令を１バイト（１６進数２桁）ずつ手作業でメモリに並べていってから、実行開始番地を指定し、「ここから実行せよ」と命令しなければなりませんでした。

さて、これからの作業は、この計算部分だけをアセンブリ言語で書いてみようというわけです。Ｃ言語のmain関数は次のようになりますね。
~~~c:with_asm.c
/*** add by assembly language (with_asm.c) ***/

#include <stdio.h>

extern unsigned short asmadd();



int main(int argc, char **argv)

{ unsigned short answer;

  answer = asmadd();
  printf("%x\n", answer);

  return 0;
}
~~~

### ２　汎用レジスタ

レジスタはメモリと違ってＣＰＵの中にあります。汎用レジスタはax、bx、cx、dxの４個、いずれも２バイトのサイズです。４個の性能はほとんど同じですが、main関数に値を返すときはaxを使うという約束になっています。
２バイトといえば、私が使っているgccコンパイラはunsigned shortが２バイトですから、両者は同じサイズですね。
２バイトのサイズは0x0000から0xFFFFまで、ちょうど0x10000個の数値を扱うことができます。十進数では65536、技術屋さんは「ムコのゴンザブロウ」と覚えるのだとか。

### ３　アセンブリ言語による関数（第１案）

それでは汎用レジスタを使って、関数を書いてみます。

~~~asm:asmadd_1.s
/*** addition by assembly language (asmadd_1.s) ***/

	.globl	asmadd		/* global .. called by 'main' */
	.type	asmadd, @function

asmadd:
	movw	$0, %ax		/* initialize answer */
	movw	$100, %bx	/* set limit (actually constant) */
	movw	$1, %cx		/* initialize addend */

asmadd_loop:			/* start of 'do' loop */
    addw	%cx, %ax    /* ax += cx */
	addw	$1, %cx     /* cx++ */
	cmpw	%cx, %bx	/* try (bx - cx) */
    jnc	asmadd_loop	    /* loop if no borrow */

	ret
~~~

さっきＣ言語で書いたmy_first.cと、意外に似ていますね。movは代入命令で、カンマの右側を左側と同じにします。moveのつもりでしょうが、むしろコピー命令と言いたい感じです。addはもちろん加算命令です。
cmpは比較命令で、カンマの右側から左側を一応引いてみよと命じています。減算命令subと違って右側の値は変わりません。
次の命令は「（比較命令の結果として）桁借りが生じなければジャンプせよ」です。加数が１から１００までは桁借りはありませんが、１０１になると１００マイナス１０１で桁借りを生ずるのでループを抜けます。（注１）
さて、コンパイルの対象ファイルが２つになったので、コンソールから
~~~asm:
      gcc with_asm.c asmadd_1.s
~~~

と指示します。実行可能ファイル（デフォールトでa.out）を実行すれば、答えの13baが表示されるでしょう。
ついでに、thelimitの数値を100でなく400にして、再コンパイルしてみて下さい。答えは５桁になるので桁あふれを生ずるのに、マシンは何食わぬ顔でまちがった答えを表示します。（注２）

### ４　桁あふれと桁借り

汎用レジスタ、たとえばaxについて、加算と減算をやってみます。いずれも１６進数です。
    0006 + FFFC = 0002
    0002 - FFFC = 0006
加算命令の正解は10002ですが、axは４桁しかないので桁あふれ（キャリーオーバー）を生じ、結果はただの２になっています。
同様に減算命令では被減数が桁借り（ボロウ）をやって答えの６を出しています。
さっきの関数ではこの桁借りの有無を調べ、「ボロウがなければループを続けよ」と命令したのですね。
ところで、さっきの式を見ると、桁あふれや桁借りを無視すれば、FFFCはマイナス４と同じであることがわかります。axをsigned shortとして使うときはこの性質を利用します。具体的にはsigned shortでは最上位ビット（most significant bit）がゼロならば非負、１ならば負数とみなします。つまり
    0001から7FFFまでは正数
    8000からFFFFまでは負数
です。
なお7FFFに１を加えると突然マイナスになってしまいますが、この現象は桁あふれとは全く違うもので、キャリーオーバーとは言いませんからご注意を。

### ５　キャリーフラグ

ＣＰＵの中にはaxなどのレジスタのほかに、「キャリーフラグ」と呼ばれる１ビットのレジスタがあります。１ビットですから値は０か１のどちらかです。
計算命令の結果としてキャリーオーバーやボロウを生じると、キャリーフラグは１になり、生じなければ０になります。さっきの関数のjncをハードウェア寄りに表現するならば、「キャリーフラグが立っていなければジャンプせよ」です。
キャリーフラグの値は外からの命令で変えることができます。キャリーフラグを０にする命令はclc、１にする命令はstcです。

### ６　アセンブリ言語による関数（第２案）

加数の初期値を１００にして逆順に足し込むと、関数はかなり短くなります。「何とミミッチイことを」と言われそうですが、こういうのはアマチュアにとって最高の知的ゲームで、みんなが楽しんで工夫していました。雑誌によく「こう書けば２バイト節約できる」などのノウハウが載っていたものです。

~~~asm:asmadd_2.s
/*** addition by assembly language (asmadd_2.s) ***/

	.globl	asmadd		/* global .. called by 'main' */
	.type	asmadd, @function

asmadd:
	mov	$0x0000, %ax	/* initialize answer */
	mov	$0x0064, %cx	/* initialize addend */

asmadd_loop:			/* start of 'do' loop */
	add	%cx, %ax        /* ax += cx */
	dec	%cx	            /* cx-- */
	jnz	asmadd_loop	    /* loop if not zero */

	ret
~~~

閑話休題、dec命令は初登場ですね。プログラミングでは「１を加えよ」「１を引け」は非常に多く出てくるので、inc、decという命令が用意されています。ちょうどＣ言語でi++やi--があるのと同じです。
第１案で説明するのを忘れましたが、命令の末尾に付いていたwは、「この命令の対象はワード（２バイト）」という意味です。しかし右側を見れば命令の対象が２バイトなのは一目瞭然ですから、今回はばっさり省きました。なお私は自分自身の混乱を防ぐため、アセンブリ言語の数値はすべて１６進数で書くことにしています。
それでは第１案を第２案に差し替えてコンパイルして下さい。
~~~asm:
      gcc with_asm.c asmadd_2.s
~~~
実行結果はもちろん同じですよね。

### ７　ゼロフラグ

ＣＰＵの中にはキャリーフラグのほかに、「ゼロフラグ」と呼ばれる１ビットのレジスタがあります。これも１ビットですから値は０か１のどちらかです。
計算命令の結果がゼロになると、ゼロフラグは１になり、それ以外は０になります。ここでもjnzをハードウェア寄りに表現するならば、「ゼロフラグが立っていなければジャンプせよ」です。
ゼロフラグはキャリーフラグと違って、外から値を変えることはできません。

### ８　フラグに関する注意事項

キャリーフラグもゼロフラグも、計算命令の結果として変化するものであり、代入命令では変化しません。次の２つを見比べてください。
        sub %ax, %ax
        mov $0x0000, %ax
どちらの命令もaxの値をゼロにしますが、前者は計算命令です。ボロウはないのでキャリーフラグは０、計算結果はゼロなのでゼロフラグは１になります。後者は代入命令ですからフラグは前のままです。
もうひとつ、incとdecではゼロフラグは変化しますが、キャリーフラグは変化しません。理由を訊かれても「ＣＰＵの造り方がそうなっているから」とお答えするしかないのですが、とにかくincとdecは正式の計算命令addやsubとは微妙に違うことを意識しておく必要があります。

### ９　レジスタのサイズ

世の中で動いているx86系のパソコンは、みんなレジスタのサイズが８バイトです。（少しは例外があるかも）このサイズのレジスタはraxとかrbxという名前で使われます。
フルサイズの右半分、つまり下位４バイトはeaxとかebxという名前です。左半分だけを使うことはできません。
ハーフサイズの更に右半分が、おなじみのaxとかbxで、サイズは２バイトです。
このような世の中ですから、趨勢としてはクォーターサイズのaxやbxよりもハーフサイズが愛用される傾向にあります。われわれも漸次ハーフサイズに移行することにしましょう。

### １０　コンパイラとアセンブラの役割分担

コンパイラはみんな中間生成物としてアセンブリ言語を作り、それを機械語に翻訳しています。私のgccはasというアセンブラが裏で協力しています。コマンドラインでたとえば
~~~asm:
gcc -S my_first.c
~~~



（Sは大文字）と書けば、gccがasに渡すアセンブリ言語のファイルを得ることができます。
この前半の仕事、人間の書いたＣ言語をアセンブリ言語に変える段階では、作業はまことに自由自在、変数をどのレジスタに割り当てるかも勝手に決められるし、気の利いたコンパイラは第１案の原作を第２案のように直してメモリを節約するかもしれません。それどころか@negi-drumsさんによれば、原作を「100 * (1+100) / 2」に変えてしまうコンパイラもあるそうです。（[https://qiita.com/negi-drums/items/054cb231b88bc7040e03](https://qiita.com/negi-drums/items/054cb231b88bc7040e03)）
後半の仕事は全然違います。アセンブラには気配りや親切心はありません。人間がカンマの前後のレジスタ名を逆に書いても、初期化しないレジスタをそのまま使っても、アセンブラは黙々と逐語訳するだけです。
私はケチで:sweat_smile:モノを捨てない性格なので、マイクロソフトのmasm（バージョン５だったか）を今でも持っていて、ときどきMS-DOS上で使って遊んでいます。このアセンブラは文法こそ違うものの（注３）、asと全く同じ機械語を生成します。
少し極言かもですが、アセンブラはＴＫ－８０で機械語を１バイトずつ手作業でメモリに並べていった、あの灰色の作業を代行するだけのツールに過ぎないのです。

追記
これは最初に書くべきでしたが、私のコンパイラはgcc6.2.1で、LinuxのFedora25に付いています。マシンはレノボ、ＣＰＵはIntel Core i5-6300Uで、普段はウィンドウズ７が走っています。

（注１）
「見ればわかる」ので説明を省きましたが、レジスタの前には%を、ナマの数値の前には$を付けることになっています。
ちょっと違和感を覚えるのは、操作の対象がカンマの右側にあることです。Ｃ言語などでは「a = b;」など、操作の対象は左側ですから、ちょうど逆ですね。これは文法なので慣れるしかありません。

（注２）
Ｃ言語のレベルでは桁あふれを検知することはできません。アセンブリ言語ならば簡単で、答えに加数を加える命令の後にjc、つまり「桁あふれを生じたらジャンプせよ」と書けばループを抜けることができます。

（注３）
文法が違うだけで、命令そのものは同じです。因みに文法名は前者がAT&T syntax、後者がIntel syntaxと呼ばれています。