プログラム制御


  1. 概要

     前回に引き続き、計算機がプログラム記憶に内蔵されたプログラムを実行する仕組みを説明します。ここでは、プログラムで繰り返しや条件付の実行を行うときの「からくり」について説明します。

  2. 演算命令とプログラム(復習)

    1. 式のコンパイルと記号プログラム

      先回、C言語の式
       a3 = a0 + a1 + a2 ;
      は、次のように複数の命令で実行されることを学びました。ここで、01010、01011などは変数 a0,a1,.. の番地になります。
        LDM  01010
          ADM  01011
          ADM  01100
        STM  01101
       ついでに番地も記号化します。01010 から順に a0, a1, a2 と書くことにします。すると、上のプログラムは次のようになります。a0,a1 などは番地を表す記号なので「記号番地」と呼びます。
        LDM  a0
          ADM  a1
          ADM  a2
          STM  a3
      このように命令や番地を記号化したプログラムを記号プログラムとかアセンブリ言語プログラムと呼びます。

    2. 命令の構成

       命令はつぎのようにメモリに記録することができます。命令は
         命令の種類  実行する値(メモリの番地)
      から構成されています。そこで、命令を11ビットで表現することとし、上位3ビットで命令の種類、下位8ビットで命令が実行する値を記録することにします。LOD を 100、ADM を 101 で表現すると、先のプログラムは
       100 01010
        101 01011
        101 01100
        101 01101
      となります。このように 0,1で表現されたプログラムを、機械語プログラムとよびます。CPUはこの機械語命令を読みだしながら、計算処理を行います。
       式の計算を単純な演算分解することで、命令の構造を簡単にすることができます。
    3. 命令を連続実行する仕組み

        次にこの機械語プログラムを連続実行する仕組みを考えます。この仕組みの中心は、実行する命令の番地を記憶する レジスタ:PS と、読み出した命令を記憶するレジスタ:IR です。次のような仕組みを考えます。
          PS 番地のメモリの値をレジスタ IR に読み出す。
          PS を一つ増す
          IR に読み出した値に従い、命令を「実行」する
          以上を繰り返す
       レジスタの値を一つ増すには、PS をカウンタ(計数器)として動作させることになります(カウンタ回路の詳細はここでは省略します)。命令を「実行」するには、IR レジスタの先頭3ビットをチェックし、100 なら LDM 、101 なら ADM の処理を行う事になります。この方式を、プログラム蓄積(Stored Program)方式、あるいはノイマン(最初にこの方式を紹介した人の名前)方式と呼びます。命令の実行で「PSレジスタの値を変更する」ことで複雑な制御が可能になります。

  3. 繰り返しとジャンプ命令


    1. 繰り返し

       上記の演算命令だけでは命令を順に実行するだけです。もう少し高度なプログラム制御が望まれます。そこで考え出されたのが、実行の流れを変える「制御命令」です。制御命令は次に実行する番地を指定する命令です。
       最初の 0: は命令の記憶された番地です。0,1,2 番地で初期設定と演算を行います。次の JP(JumP)命令で1番地に「ジャンプ」します。これで、1,2,3 番地のプログラムを繰り返し実行できます。
      0: 初期設定
      1: 演算処理1
      2: 演算処理2
      3: JP 1  //1番地にジャンプ
       この、ジャンプ命令の「からくり」は意外と簡単です。ジャンプ命令の実行で(演算命令のようにWを変更するのでなく)PS レジスタの値を変更すればよいのです。JP命令では命令で指定した値(番地)を PS レジスタに設定します。プログラム制御では PS レジスタの番地の命令を実行しますから、PS レジスタの値を変更すれば、次は変更した番地の命令を実行します。
      現在のPS 実行する命令 次のPS
      0 初期設定 1
      1 演算処理1 2
      2 演算処理2 3
      3 JP 1 1
      1 演算処理1 2
      2 演算処理2 3

    2. 条件付繰り返し

       これで、繰り返しはできるようになりましたが、無条件の繰り返しでは、繰り返しを停止できません。たとえば、 A * B は「B>0 のときA をB 回加える」ことで計算できです。指定した回数だけ繰り返しができれば、加算命令で乗算ができるようになります。
        この指定回数の繰り返しを行うには「繰り返し条件」を設定するためのレジスタ(記憶)が必要です。このレジスタをフラグレジスタ:FR と呼びます。FRレジスタには演算結果が0になった場合セットする ZF(ZeroFlag:ゼロフラグ)、演算結果が負になったときセットするSF(SignFlag:符号フラグ)、などが含まれます。
       ジャンプ命令を実行をするとき、フラグがセットされているときのみ PS を変更してジャンプ処理を行い、セットされていないときはPSを変更しないで、次の命令の実行に移ることにします。この条件付ジャンプを利用すると、10回の繰り返しは次のように行うことができます。
      0: Wを10にする
      1: Wをc0番地に記録する
      2: 演算処理1
      3: 演算処理2
      4: c0を1減らす。(このとき結果が0になったら ZF が1にセットされる)
      5: ZFが0のとき1番地へジャンプする。そうでなければ、次の命令に進む
      6:
       c0 はメモリの特定の番地(たとえば8番地)を利用します。最初に 0 ,1番地の命令で c0(8番地)を10に設定します。4番地の命令で c0 を一つ減らし、この結果0になったら 演算回路により自動的に ZF が1にセットされます。命令5では ZFが0(1でない)のとき、PSを変更して1番地にジャンプします。ZFが1のときは条件が成立しませんからジャンプは行われず、5番地以後の命令に進みます。ZF が 0 とき、a 番地にジャンプする命令を
        JNZ  a
      と書くことにします。JNZ(Jump not ZF) は「条件付のジャンプ命令」と呼ばれます。また、指定した番地の値を一つ減らす命令を
        DEC  a
      と書くことにします。DECはDecrementの略です。すると、上の10回繰り返すプログラムは次のようなプログラムで書くことができます。
      0: LDI  10  //W<-10
      1: STM  c0  //c0<-W
      2: 演算処理1 
      3: 演算処理2
      4: DEC  c0 //c0--
      5: JNZ  2  //if not ZF jump 1
      6:
       c0 が 0 になるまでJNZ 2 で2番地に戻り、10回繰り返すと DEC命令で ZF が1になり、次の JNZ 命令ではジャンプをせずに6番地に進みます。
      命令 意味
      DEC a a番地を1減らす
      JP a 無条件にa番地にジャンプ
      JZ a ZFが1ならa番地にジャンプ
      JNZ a ZFが0ならa番地にジャンプ
      JS a 負ならa番地にジャンプ
      JNS a 負でなければa番地にジャンプ

    3. 条件付ジャンプの回路構成(参考)

       条件付の制御を行う回路構成は以下のようになります。ALUからの演算結果で、SF、ZF をセットします。条件付ジャンプ命令を実行すると jg に信号(パルス)が出ます。この信号は SF,ZF の条件が合致すれば、命令を記録するレジスタ IR の番地部の値を PS に移します。これで、ジャンプ(ジャンプ)が起ります。

       PS の値をIR(命令の番地部)の値で置き換えることで、ジャンプ命令が実行できることを理解してください。

    4. 記号番地と繰り返し

      以下のプログラムは a * b の計算をします(b>0とします)。0,1番地の命令で s = 0 とします。2,3,4 の命令で s = s + a を計算します。5番地で b を減らし、0でなければ2番地に戻ります。s に a をb回加えることになります。a, b 番地には予め適当な値が記憶されているものとします。a, b, s, の番地は 8,9,10 に割り当てられているものとします。また、L1: はL1を2番地と定義します。したがって、JNZ L1 は条件があえば 2番地にジャンプします。
      JP 7 は7番地で実行を繰り返しプログラムの進行を止めます。停止命令と同じ意味になります。
      0:  LDI  0  //W <- 0
      1:  STM  s  //s <- W  
      2:L1:LDM  s  //W <- s
      3:  ADM  a  //W <- W + a
      4:  STR  s  //s <- W
      5:  DEC  b  // b--
      6:  JNZ   L1 //if notZF goto 2
      7:L2:JP   L2 //動的停止
      8:  a:  5 //加える値
      9:  b:  10 //加える回数
      10: s: 0 //結果

       以下のC言語のプログラムは上とどうような処理を行います。if(条件) goto ラベル は条件が満たされたら ラベル に進む(飛ぶ)ことを意味します。 L1: は goto 文のラベル(飛び先)を定義します。
      #include "stdafx.h"
      int _tmain(int argc, _TCHAR* argv[])
      {
           int a,b,s;
           s = 0;
           a = 10;
           b = 5;
      L1:  s = s + a;
           b--;
           if(b != 0) goto L1;
           printf("%d\n",s);
           return 0;
      }
       通常、C言語では繰り返しは goto 文を使わずにつぎのように while や for を利用してプログラムをします。 goto 文を利用したプログラムはプログラムの流れが理解しにくく、間違いを起こしやすいからです。ただし、CPUには for や while の命令はなく、コンパイラが 条件付き goto(Jump) 命令に置き換えているのです。
      int _tmain(int argc, _TCHAR* argv[])
      {
           int a,b,s;
           s = 0;
           a = 10;
           b = 5;
           do{
                s = s + a;
                b--;
           } while ( b != 0);
      
            printf("%d\n",s);
           return 0;
      }

    5. ステップ実行

       VisualStudioでは 1文単位で命令を実行することができます。プログラムを作成(ビルド)後、デバッグメニューのステップイン(F11)を選択します。これで、プログラムの最初の文が実行されます。続けて F11 キーを押すと、文を順に実行します(左の黄色い矢印が次に実行する文を示します)。ステップ実行するとき、各変数の値が、ウオッチウインドウに表示されます。ステップ実行すると、プログラムの流れが理解できます。
        

  4. 条件付き実行

    1. 条件付実行を行うプログラム

       条件付ジャンプは、左下のような流れで実行できます。これは、C言語で
         if (条件)  A;
      と書きます。右下は、無条件ジャンプを組み合わせた例で
       if (条件) A else  B ;
      の流れです。条件付実行は条件付ジャンプ命令と無条件ジャンプを組み合わせることで、実行が実現できます。
      (下図で分岐はジャンプと同じ)

       条件付きジャンプ命令を利用し、前の番地にジャンプすれば繰り返し(while、for)が、先の番地にジャンプすれば条件付き実行(if、else)ができることになります。

    2. 例(絶対値)

      次の例は、aに値を保存し、a の絶対値を計算する記号プログラムです。LDMではSF(符号のフラグ)はセットされませんから、0を引き算しています。
         LDI -5
         STM  a  //a=-5
         SBI  0  //a > 0 ?
         JNS  L1 
         LDI  0  //a = -a
         SBM  a
         STM  a
      L1:  
      同じような処理を C言語で行います。printf( )は結果の確認用です。
      int _tmain(int argc, _TCHAR* argv[])
      {
             int a;
             a = -5;
             if (a > 0) goto L1;
             a = -a;
          L1:printf("%d\n",a);//確認
             return 0;
      }

       
    3. 条件付きジャンプとコンパイラ

       言語C以前のプログラミング言語(FortranやBasic)などでは、while や if などの制御文はなく、if  goto で繰り返しや条件付き実行を記述していました。しかし、これではプログラムの流れがわかりにくいので、goto を排除し、while・if による記述が推奨されるようになりました。このようなプログラミングを gotoレスとか、構造化プログラミングと呼びます。
       計算機内部では、構造を簡単化するため、条件付きジャンプ(if goto )命令で制御を実行します。コンパイラが構造化プログラムを条件付きジャンプ命令のプログラムに変換します。

  5. 演習(課題)、アンケート

    1. 演習および課題

      以下の課題を演習しなさい。結果を次回までに提出しなさい。

      課題1
       1から10までを加えるプログラムを、記号プログラムおよびC言語(goto利用)、で作成しなさい。

      課題2
       a と b の大きい方を 変数 max に保存するプログラムを C言語(goto利用)で作成しなさい

      C言語プログラムは実際に実行しプログラムとその実行結果を印刷して提出すること(課題1,2共通)。
      ステップ実行を行い、そのハードコピーを添付すること。
    2. アンケート

       1.プログラム内蔵方式の説明で最も適しているものを選べ
         1.運動会のプログラムと同じである  2.専用回路と比較し高速に実行できる  
         3.プログラムの変更が容易である   4.ベルトコンベアによる実行と同じである。

       2.制御命令の説明として適しているものを選べ
         1.無条件に指定番地にジャンプする  2.繰り返しのみができる  
         3.条件によりWレジスタを変更する   4.条件によりPSレジスタを変更する

       3.条件付ジャンプ命令に関し不適当なものをえらべ
        1.case文を実行できる  2.else 文を実行できる  
        3.関数が実行できる   4.C言語にも条件付ジャンプをする文がある。

       4.繰り返しや条件付実行などを行う制御のからくりは理解できましたか
        1.理解した  2.だいたい理解  3.ちょっと不安  4.わからん