2014年3月30日日曜日

C/C++ 処理の流れ その1

コンピュータでは色々な計算をして、適切なタイミングで計算結果を表示したり、音にしたりでI/Oに出力します。

その処理の流れには主に以下があります。
  1. 逐次実行
  2. ジャンプ
  3. サブルーチン
  4. 条件分岐
  5. 繰り返し
  6. 並列実行

逐次実行


逐次実行は一番基本的な処理の流れで、順番に処理をこなしていくだけです。C/C++言語も最終的にはCPUにおいて機械語で命令が実行されますが、音楽の手拍子のように決まったタイミングで命令が実行されていきます。この手拍子の速度がクロック周波数と言われるもので、基本的にはクロック周波数が速いということになり、パソコンの速度を示す指標に使われます(実際には1回の手拍子で2つの命令を実行することができたり、クロック周波数だけで速度は決まりません)。

C/C++言語では、基本的な実行は逐次実行となります。演算において、

    a = b + c + d;

とあれば、b と c の加算を行い、その結果と d の加算を行い、その結果をaに代入すると順番に実行されていきます(演算の順番については演算の優先順位と言うものが存在し、その順で実行されていきます)。

また、以下のように

    処理1;
    処理2;
    処理3;

とセミコロンで処理を分けた場合にも順に処理1、処理2、処理3と順番に実行されていきます。

ただし、処理が関数の場合、関数の中で並列実行がされている可能性があり、その場合は処理2が完全に終了する前に処理3が行われるケースもあります。最近のWindows8での Windows Store アプリ用の関数(WinRT API)は並列実行が基本となっているので注意が必要です。

ジャンプ


ジャンプは、処理を別の場所に飛ばすことになります。通常は後述の条件分岐と一緒に使うことになります。

C/C++言語では

 goto ラベル

とすることで、ラベルの位置にジャンプします。例えば以下のプログラムではlabel5というのがラベル名です。コロン(:)を付けることでラベル名であることを示します。

#include <stdio.h>

int main()
{
        printf("1\n");
        printf("2\n");
        goto label5;

        printf("3\n");
        printf("4\n");

        label5:
        printf("5\n");
        return 0;
}

このプログラムでは、1,2と表示したところで、label5にジャンプするので、3,4は表示されずに5が表示されます。

関数をまたぐ場合にジャンプを使用するには、setjmp.hを使用して、setjmp()関数、longjmp()関数を使用します。

C/C++言語では、構造化プログラミングと言って、処理を階層的に表し、入口と出口をはっきりさせる記述方法が奨励されており、その立場ではジャンプはあまり好ましくない手法とされています。しかし、使い方によってはよりプログラムを分かりやすくする場合もあり、一概に悪いとは言い切れないです。初心者はまずは使わない方が良いでしょう。

サブルーチン


プログラムを見やすくするために処理を一つにまとめたものをサブルーチンと言います。プログラミング言語によっては、単に処理をまとめた「サブルーチン」と、返り値を必要とする「関数」を別に扱う言語もありますが、C/C++言語では「サブルーチン」と「関数」は同じ扱いになっています。下記がその例で、func1とfunc2のサブルーチン(関数)を使っています。

#include <stdio.h>

void func1(void);

void func2(void)
{
        printf("func2 1\n");
        printf("func2 2\n");
        return;
}

int main()
{
        printf("main\n");
        func1();
        func2();
        printf("main end\n");
        return 0;
}

void func1(void)
{
        printf("func1 1\n");
        printf("func1 2\n");
        return;
}


main()関数からfunc1()関数とfunc2()関数に処理が飛び、それらの関数からreturnでmain()関数に戻ってきます。C/C++言語ではジャンプより、このようなサブルーチンを使って処理を移動することが奨励されています。

なお、func1, func2の前にあるvoidが返り値が無いことを示しています。return文でも返り値を書いていません(このように返り値が無い場合にはreturn文を省略しても良いです)。

また、main()関数で、func1()関数が使用されていますが、func1()関数はmain()の後ろに書かれています。C/C++言語は上からプログラムが解釈されていくため、main()関数で突然func1()関数が出てきても理解できません。そのため、3行目でfunc1()という関数がどこかで書かれていることを示しています。

注意点としては、サブルーチンは機械語的になったときには、スタックというメモリ領域に返り値が覚えられるため、サブルーチンを入れ子にして使いすぎるとメモリをたくさん使用してしまう問題があります。


return文でのジャンプ


上述のように関数(サブルーチン)を終了するときには、return文を使用します。return文は処理の最後である必要はないですし、数も一つの関数に一つというわけではなく、いくつ書いても構いません。

しかし、構造化プログラミングの、一つの入口に一つの出口の観点からは関数においてreturn文は最後に一つにすべきとのルールもがある場合があります。実際にそのように作った方がリソースリークという問題は起きにくいです。

ただ、このルールもやり過ぎるとネストが深くなったり、かえってプログラムが見にくくなる場合もあります。

個人的には引数のエラーチェック等を関数の初めの方にに行い、エラーを返すような場合には、途中のreturn文を書いていいと思っています。そして、何より関数の行数を長くしすぎないことが重要です。

条件分岐処理や繰り返し処理はまた次回。


0 件のコメント:

コメントを投稿