2014年4月30日水曜日

低価格SIMフリー端末 freetel を使ってみた


低価格で節約してスマホを運用するためのMVNOがテレビや新聞などで紹介されてくるようになってきました。しかし、残念ながらSIMフリーの端末はあまり出ていないように思われます。なかなかSIMフリーの時代が来ないものです。

現在のMVNOはDTI Serversman SIM LTEなどもすべてdocomoの回線を使用しているため、docomoの端末でも低価格のメリットは得ることができると思いますが、SIMフリー端末が出ないと最終的に真に自由な競争による低価格化や発展はない気がします。

2014年4月現在に日本で買えるSIMフリー端末は以下だと思います。低価格を基準にしているので種類が多いのは安い物を選んでいます。また、一応タブレットでなく、電話の出来るスマホと言える端末のみです。もちろん技適マークが無い海外製のスマホは、安くても日本で使ってはダメなので、含みません。

使ったことが無い端末がほとんどなので、参考には全くなりませんが、簡単に知っている利点も書いてみました。

端末価格利点
freetel11,967円 (Amazon)日本でのサポートが非常によく、一番安い。2つのSIMが使えるので海外で使うのに便利。バッテリー交換可能。
ASUS Fonepad 7 TABLET 27,243円 (Amazon)量販店で買える?7inchと画面が大きい。
PolaSma27,999円(税抜)トイザらスで購入できる。子供用で、子供にも安心して使える機能が用意される。 2つのSIMが使える。
Nexus 5 16GB40,937Google謹製。Androidのアップデートが早い。開発者向けな情報が多い。約5inchの画面にFullHD(1920x1080)。
iPone 5c 16GB57,800日本では一番売れているApple製品の中では一番安い。




freetelを使ってみた


上記の中で圧倒的に低価格なfreetelを買ってみました。もちろんNexus 5の方が全てにおいて良いですし、安物であることには変わりないでしょう。しかし、どの程度の事をスマホに求めているかにもよりますが、あまりスマホに依存していない私としては、これで必要十分な機能を持っているのではないかと思いました。




最初から入っているソフトウェアは以下のみでした。Nexus 5よりも少ないです。
  • カメラ
  • カレンダー
  • ギャラリー
  • ダウンロード
  • タスクマネージャー
  • ブラウザ
  • メール
  • メッセージ
  • ユーザー
  • 音楽
  • 音声レコーダー
  • 検索
  • 時計
  • 設定
  • 電卓
  • 電話
  • 動画プレイヤー
  • GMail(マイアプリにも表示)
  • Google設定
  • Google日本語入力(マイアプリにも表示)
  • Playストア
個人的には音声での検索(Google Now)、地図はAndroidを使いやすくするには必須と思ったため、以下を追加してインストールしました。音声でのアラームや通知の設定や、電車の時刻の検索もできますし、ナビももちろん使えます。
  • Google検索
  • Googleマップ
その他、Youtubeも入れています(ブラウザで見るより読み込みが速く思えたため)。優先インストール先がSDカードになっているので、だいたいこの時点で141MB使用、79MBの空きとなっています。SDカードに入らないアプリを使用しない工夫が必要そうです。しかし、大量に入れたいアプリがある人(特にゲームなどをする人)でなければ大丈夫でしょう。

スペック表などで分かりにくい欠点としては以下があります。
  • USB接続用コード、イヤホンが専用となっている。
  • 裏蓋が外しにくい。
  • 内蔵メモリが少ないので、どのアプリを入れるか考える必要がある。
  • 液晶の視野角が上下方向に非常に狭い。
  • バッテリーがあまり持たない(持って1日程度)。
  • 同じDTI ServersMan SIM LTEを使用してもNexus5より遅い?(対応周波数が狭いため?)。Youtubeなどを見ると遅くて止まるのですが、その頻度が高い気がする。なお、DTI ServersMan SIM LTEの設定はプリセットで入っているので、設定はNexus 5より簡単で選ぶだけです。
  • GPSのつかみが非常に遅い(いつまでも終わらない)。ただし、A-GPSを使えばだいたいの位置はすぐに表示される。
  • ピンチイン、ピンチアウトが縦方向で効かない。
  • タッチの反応が少し下側にずれる(ボタンなどを気持ち上側を押す必要がある)。
  • 付属のSDカード(8GB)がすぐ(2週間程度)で壊れる。 
  • ボタンを押す音が少々うるさい。
たくさん悪い所を書いてしまいが、逆に言えばこれ以外は普通によく出来たAndroid端末で、速度もそれほど悪くはありません。性能と価格を考えるとこれで十分と思えてしまいます。今後、もっとこういう端末が増えて、更に安く高性能になって行って欲しいです。


freetelでAndroidのソフトウェア開発


freetelは性能的には、Nexus 5よりかなり劣りますが、逆にそれがAndroidのソフトウェアのテストにも使えて良いかもしれません。

Windowsでは、freetelをUSB接続したときに表示される「USB settings」の画面で、「USBバーチャルドライブ」を接続したときに入っているファイル"ADB.RAR"を解凍して、adb_install.batを実行すると、開発できるようになるようです。

Fedora 20では、下記を実行することでfreetelがUSB接続で認識され、Eclipseからプログラムを実行できるようになりました。最後のadb devicesコマンドにおいてListにxxxxxxxx(この部分は数字です)が表示されていれば大丈夫だと思います。Ubuntuでもsystemctl以外は大体同じで大丈夫だと思います。

$ echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="1782", ATTR{idProduct}=="5d04", MODE="0666"' > 51-android.rules
$ sudo cp 51-android.rules /etc/udev/rules.d/
$ sudo systemctl restart systemd-udevd
$ echo 0x1782 > ~/.android/adb_usb.ini
$ adb kill-server
$ adb devices
* daemon not running. starting it now on port 5037 *
* daemon started successfully *
List of devices attached 
xxxxxxxx        device


前にNexus 5 で作成したプログラムをfreetelで実行すると以下のようになります。


C/C++でOpenMPを試してみる

OpenMPとは?


OpenMPとは、並列プログラミング用の 機能で、C/C++, Fortran で使用できます。並列プログラミングには色々とありますが、以下の様な利点があります。
  • 通常のプログラムからOpenMPを使用するように書き直しやすい。
  • ハード(CPUのコア数)に依存しないプログラムが書きやすい。
  • Visual Studio(Pro2005以降), gcc(4.2以降), インテルコンパイラ(V9以降)などある程度マルチプラットフォームで使用できる。

逆に欠点としては以下があります。
  • 記述に#pragmaディレクティブというコンパイラ専用の機能の記述に使用する方法を使用するので、プログラムが見にくくなる。
  • パフォーマンスは特にコア数が増えると今一歩のところがあるらしい。
  • 現在OpenMP4.0まで出ているが、Visual StudioはOpenMP2.0まで、LLVMは現在OpenMP3.1に対応中と対応に差がある。


OpenMPをC/C++で使うには?


OpenMPが使用できるかどうかをチェックするためには、_OEPNMPが定義されているかどうかで判別します。本来ならOpenMPの機能を使う部分はすべて

#ifdef _OEPNMP
#include <omp.h>
#endif

と書き、無視されるようにした方が良いでしょうが、今回の例では省いていますので注意してください。

#pragmaは認識されなかった場合は無視されるので、_OPENMPでの#ifdefは必要ないです。omp_get_thread_num()関数などのOpenMP用の関数はコンパイラが対応していても、OpenMP用のコンパイルオプションを付けないとエラーになります。

コンパイル時には、 VisualStudioの場合、/openmp をつけてコンパイルすれば良いようです。gccの場合は、-fopenmp をつければ良いです。今回はgccで動作確認しています。MinGW環境でも、動作します。

omp_set_num_threads()関数などOpenMPの関数を使用する場合にはヘッダーファイルとしては"omp.h"をインクルードします。


並列実効領域構文 ー parallel構文


OpenMPを使うときには、まずはprallel構文で並列実効領域の指定をします。以下のようになります。

#include <stdio.h>

int main()
{
        #pragma omp parallel
        {
                printf("parallel\n");
        }
        return 0;
}

#pragma omp というのが、OpenMP の指令であることを示しています。-fopenmpを指定しない場合は無視されます。

parallel構文では、{}で括られた部分が、CPU毎にスレッドが作られ実行されます。例えば2つのコアのCPUの場合は、二回printfの実行が行われます。

より詳細に説明すると以下のようになっています。
  • { は #pragmaと同じ行には書けません。実行が一行のみの場合は {} は省略できます。
  • スレッド数は指定することも可能です。方法はいくつかあります。
    1. 環境変数 OMP_NUM_THREADS に数を指定する。
    2. #pragma omp parallel num_threads(数)と後ろに指示句num_threadsをつける。
    3. omp_set_num_threads(数)関数で指定する
  • omp_get_num_threads(数)関数でスレッドの数を取得したり、omp_get_thread_num()関数でスレッド番号(0,1,...)を得ることで、スレッドごとの処理をすることができます(ただしあまり必要ないです)。


ワークシェアリング構文1 ー for構文



for構文は、後に続くfor文を自動的にスレッドの数に分解して実行してくれます。


#include <stdio.h>
#include <omp.h>

int main()
{
        char data[100];
        int i;

        #pragma omp parallel
        {
                #pragma omp for
                for (i = 0; i < 100; i++) {
                        data[i] = 0;
                }
        }
        return 0;
}


parallel構文で作成されたスレッドが2つの場合、i を 0~49, 50~99 に分けて data[i] = 0; を実行します。

parallel構文とfor構文を合わせて以下のように書くこともできます。

        #pragma omp parallel for
        for (i = 0; i < 100; i++) {
                data[i] = 0;
        }

データをどのようにスレッドで分割するかを指定することもできます。通常は static ですが、dynamic や guided が指定できます。指定は、#pragma omp for schedule(タイプ)とします。
  • static - データをスレッド数で分割して実行します。
  • dynamic - 終わったスレッドからデータを取ってきて実行します。
  • guided- static + dynamic。徐々にstatic分を減らしていく。最も効率的らしい。
  • runtime - 環境変数OMP_SCHEDULEに従って、static,dynamic,guided を切り替え。

以下は、スレッド番号が小さいスレッドほど時間が掛かるようにして、スケジュールの違いを分かるようにしたつもりのプログラムです。

#define DATA_SIZE 20
#include <stdio.h>
#include <omp.h>
#include <unistd.h>

void exec_task(int i)
{
        int id;

        id = omp_get_thread_num();
        printf("%d - start %d\n", id, i);
        usleep(i * 100000);
        printf("%d - end   %d\n", id, i);
}

int main()
{
        int i;

        #pragma omp parallel num_threads(2)
        {
                #pragma omp for schedule(static)
                for(i = 0; i < DATA_SIZE; i++) {
                        exec_task(i);
                }
        }
        return 0;
}

staticの場合には、0番のスレッドで0〜9のデータの処理を行い、1番のスレッドが時間が10〜19のデータの処理を行っていますが、1番スレッドで時間がかかっているのがわかると思います。

staticをdynamicにすると、1番と2番でほぼ交互にデータを分けて処理することがわかると思います。

staticからguiedeにすると、15,16,17のデータは0番のスレッドで処理されていることがわかると思います。
 
どのscheduleも(static, 3)などとチャンクサイズを指定することができます。チャンクサイズでデータの反復数を変更することができます。

ワークシェアリング構文2 ー section構文



複数の処理を平行して実行します。例えば以下のように使います。

int main()
{
        #pragma omp parallel
        {
                #pragma omp sections
                {
                        #pragma omp section
                        function1();
                        #pragma omp section
                        function2();
                        #pragma omp section
                        function3();
                }
        }
        return 0;
}

スレッドの数が二つの場合、function1()とfunction2()を同時に実行します。先に終わった方のスレッドでfunction3()を実行します。

for構文と同じく #pragma omp parallel sections と省略可能です。


共有データとプライベートデータ


前述のワークシェアリング構文1 - for構文の最初の例で i と data[100] の扱いが気になった方は見えますでしょうか?

data[100] はすべてのスレッドで共有のデータになっていますが、i はスレッドごとに別のデータとなります。

基本的に、#pragma omp parallel より前で指定されたデータはすべてのスレッドから参照される共有データ、後で指定されたデータはスレッドごとのプライベートデータとなります。しかし、for文のループインデックスは例外とみなされ、プライベートデータとなります。

共有データとプライベートデータを指定することもできます。

        int a = 1, b = 2, c = 0, d;
 
        #pragma omp parallel private(a), firstprivate(b), shared(c, d)


上記の場合、a, b はプライベートデータと c, d は共有データとなります。

a と b の違いは、b はスレッド開始時にすべてのスレッドで値が初期化されます。a の場合はスレッドによっては不定値となります。

最後のスレッドのプライベートデータをparallel終了時に取得するlastprivate() 構文もあります。例えばfor文のiの値をOpenMPの処理から抜けた後に使用する場合に #pragma omp parallel for lastprivate(i) とします。

また reduction というものもあり、各スレッドにおいてはプライベートとして扱うけれど、最終的にそれらをまとめる変数を指定できます。

reduction(演算または組み込み手続き : 変数)というような指定になり、演算または組み込み手続きには + や.and. や max など様々な方法で値をまとめます。

例えば、和を求めたい場合には、以下のようになります。


#include <stdio.h>

int main()
{
        int i, sum;

        #pragma omp parallel reduction(-:sum)
        {
                #pragma omp for
                for (i = 1; i <= 1000; i++)
                        sum = sum + i;
                printf("sum = %d\n", sum);
        }
        printf("sum_result = %d\n", sum);
        return 0;
}


4コアCPUの場合は以下のような結果になり、4つのスレッドで計算された値が最終的にたされていることがわかります。

sum = 218875
sum = 93875
sum = 31375
sum = 156375
sum_result = 500500


同期構文 ー barrier同期など


parallel構文では複数スレッドが動作します。スレッドが複数動くということは、スレッド同士でデータや処理のタイミング(同期)を考える必要があります。

parallel構文を抜ける時は全てのスレッドが終了してからを抜けるようになっています。このように全てのスレッドの終了を待つのをbarrier同期といいます。


parallel構文の中では複数のワークシェアリング構文が使用できます。ワークシェアリング構文も終了時にbarrier同期が行われます。

OpenMPではワークシェアリング構文の終了時に自動的にbarrier同期が行われますが、待たないようにすることも可能で、その場合はnowait指示句を使用します。

一方、通常の演算や関数等の実行など同期が行われない物もあります。同期を取るようにするにはbarrier構文を使用します。例えば以下のようになります。

        #pragma omp parallel
        {
                #pragma omp for
                        :
                #pragma omp for nowait  ← 前のすべてのスレッドが終わってから始まる
                        :
                #pragma omp sections    ← 前の終わったスレッドから使用して始まる
                        :
                function1();            ← 前のすべてのスレッドが終わってから始まる
                #pragma omp for         ← 前の終わったスレッドから使用して始まる
                        :
                function2();            ← 前のすべてのスレッドが終わってから始まる
                #pragma omp barrier
                #pragma omp for         ← 前のすべてのスレッドが終わってから始まる
                        :                
        }

今までの例のプログラムでもprintf()を使用していましたが、排他制御がされていない関数の場合、問題が起こるかもしれません。そのような場合には、以下のように

        #pragma omp critical
        function(...);

とすれば、排他制御できます。ここで指定した関数を、別のスレッドで実行中のときに 実行しようとすると、別のスレッドの処理が終わるまで待つことになります。

critical(name)の様に名前を指定することもできます。また、{}を使えば複数行を排他制御できます。

そして、共有データの場合には、複数のスレッドからアクセスされることになり、その順番が問題になることがあります。例えばaという変数の場合には、以下のように

        #pragma omp flush(a);
        function(a);

とすることで、aの順番が保証されることになります。barrier構文と同じく、ワークシェアリング構文のfor, sections等の出口で自動的に行われますし、nowaitでは行われません。


その他、以下のような物があります。
  • #pragma omp single   ある一つのスレッドのみで実行する。barrier同期,flashあり
  • #pragma omp master 0番のスレッドのみで実行する。barrier,flash同期なし。
  • #pragma omp atmic 直後の複合代入文をatmicに行います。

とりあえず、説明はこれで終わりです。上記はOpenMP2.0までで、OpenMP3.0では omp taskが追加されたり、最新のgcc4.9で追加されたOpenMP4.0ではSIMD命令が指定できたり更に複雑な処理ができるようです。興味のある方は試してみてください。

2014年4月28日月曜日

C/C++ 処理の流れ その4 並列処理

並列処理は同時に処理を行うことで、長い処理の間にユーザーが別の処理を行えるようにするなど、使い勝手の面で重要でした。

さらに最近では、CPUも単体での性能は頭打ちになってきており、CPUを複数使って、並列に処理することで高速化することも重要となってきました。

基本的にC/C++言語での処理の流れは、単体での処理となっており、並列処理はOS依存であったりライブラリであったりと、同じような処理を行うにも方法が様々です。

最近の新しいバージョンのC/C++では、標準で並列処理をライブラリで持っていますが、普及はまだまだのような気がします。

並列処理にはプロセスとスレッドとコルーチンがありますが、一番一般的なスレッドを扱う方法について説明します。

Win32でのスレッド(C言語)


Windowsの場合は<process.h>の_beginthredex()関数を使用してスレッドを作成し、並列処理を行います。CreateThread()というWin32APIもあるのですが、こちらよりbeginthreadex()関数の方が良いです(標準ライブラリの初期化をため、標準ライブラリを使用する場合は必須です)。

#include <stdio.h>
#include <process.h>
#include <windows.h>

unsigned int __stdcall print_hello(void *dmy)
{
        int i;

        for(i = 0; i < 20; i++) {
                printf("Hello\n");
                Sleep(10);
        }
        return 0;
}

unsigned int __stdcall print_world(void *dmy)
{
        int i;

        for(i = 0; i < 20; i++) {
                printf("World\n");
                Sleep(10);
        }
        return 0;
}

int main()
{
        HANDLE th;

        th = (HANDLE)_beginthreadex(NULL, 0, &print_hello, NULL, 0, NULL);
        print_world(NULL);
        WaitForSingleObject(th, INFINITE);        
        return 0;
}

上記では、分かりやすくするためにスレッドの作成に失敗した場合等のエラー処理は全く行っていないので注意してください。

_beginthraedexで、新しいスレッド(並列処理)を作成して、その並列処理でprint_hello()関数を実行します。元の処理はそのまま次に進みprint_world()関数を実行します。そのため、print_hello()関数とprint_world()関数は同時に行われます。

最後にWaitForSingleObject()関数で、並列処理のスレッドの終了を待って、プログラムを終了します。 これを行わないと、終わる前にプロセスが終了してしまい、スレッドの処理は中断されることになります。

その他、本来であれば、printf()の処理は同じ端末に対して行うので、本来であれば排他処理が必要なのかもしれません。排他処理は同じデータに並列処理が同時にアクセスし、データが異常になるのを防ぐために行います。

詳細は、Microsoftの開発者用のサイトを参照してください。


Linux(POSIX)でのスレッド(C言語)



LinuxやMac OS X等のUNIX系ではPOSIXという規格があり、その中でpthreadライブラリが定義されています。

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void *print_hello(void *dmy)
{
        int i;

        for(i = 0; i < 20; i++) {
                printf("Hello\n");
                usleep(10);
        }
        return NULL;
}

void *print_world(void *dmy)
{
        int i;

        for(i = 0; i < 20; i++) {
                printf("World\n");
                usleep(10);
        }
        return NULL;
}

int main()
{
        pthread_t th;

        pthread_create(&th, NULL, &print_hello, NULL);
        print_world(NULL);
        pthread_join(th, NULL);        
        return 0;
}

コンパイルをするときには -lpthreadのオプションを追加する必要があります。関数名や引数がWindowsとは違いますが、やっていることは同じなのでほとんど同じ記述になります。

またMinGW環境でも利用可能です。


C++11でのスレッド


C++の2011年番のC++11では<thread>にstd::threadが追加されました。以下がprint_hello()関数とprint_world()関数を並列動作させる例です。残念ながらMinGW環境ではコンパイル時にエラーとなります。

#include <stdio.h>
#include <thread>

void print_hello()
{
        for(int i = 0; i < 20; i++) {
                printf("Hello/n");
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
}

void print_world()
{
        for(int i = 0; i < 20; i++) {
                printf("World/n");
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
}

int main()
{
        std::thread th(print_hello);
        print_world();
        th.join();
        return 0;
}


C11でのスレッド


C言語でも2011年版のC11において、<threads.h>にスレッドを扱うthrd_t型や、thrd_create()関数、thrd_jon()関数が追加されています。残念ながらgccでもサポートがまだのようです(Fedora20, Mingwでthraeds.hが無いことを確認)。


その他のライブラリ



並列プログラミングは、どちらかというと歴史が新しく色々なライブラリがあります。例えば以下です。C++11のstd::threadの元となったライブラリでC++98でも使用できます。
  • boost::thread

 std::threadなどの標準のライブラリは、低レベル(人間に分かりにくく、コンピュータ寄りになっているが、制御は細かくできる)になっていますが、より分かりやすくするためにも色々なライブラリがあります。これらではコンピュータにコアがいくつ付いているかを自動的に判定し、それぞれのコアに処理を分けて振るようになっています。
  • OpenMP
  • MPI(Message Passing Interface)
  • TBB(Intel Threading Building Blocks)
  • Intel Click Plus
  • PPL(Microsoft Parallel Pattern Library)



また、今は同じ種類のCPUを使うホモジニアスマルチコアでなく、違う種類のCPUを使用するヘテロジニアスマルチコアという種類のマルチコアがあります。違う種類のCPUとしてよく使われるのがグラフィックを描画するコアであるGPU(Graphics Processing Unit)です。

現在のGPUは3D画像を扱うのが当たり前になっており、元々大量の3次元処理が得意と言うこともあり、画像処理だけでなく一般の計算処理にも使用しようとしたものです。GPGPU(General-purpose computing on graphics processing units)と言われます。

そのようなヘテロジニアスを使うライブラリとしては以下があります。
  • OpenCL
  • CUDA
  • OpenACC
  • C++AMP(C++ Accelerated Massive Parallelism)

今の所、ヘテロジニアスではメモリ空間が分離されており、スレッドというよりプロセス的な使い方が必要となっていますが、AMDが推進しているHMA(Heterogeneous System Architecture)のhUMAではメモリも統合されており、使い勝手も向上するのでは無いかと思います。


他にも一応、複数の演算を並行して行うという点では、SIMD演算と呼ばれるCPUの以下の命令も並列処理と言えるかもしれません。一つの命令で複数のデータの演算を行うことができます。
  • MMX, SSE, AVX(x86/x64)
  • NEON(ARMv7)
  • AltiVec, VMX(Power)
  • VIS(SPARC)
  • MIPS-3D, MDMX(MIPS)

CPUごとに命令が違うのですが、これらをC++上から同様に扱うMicrosoftのDirectXMachやlibsimdppなどのライブラリもあるようです。

上記のように並列処理には色々な実装があります。いつかは標準でもっと簡単に使えるようになって欲しいですが、現状ではOS標準のAPIの使用が一番いいかもしれません。


2014年4月13日日曜日

C/C++ 処理の流れ その3 繰り返し

コンピュータが一番得意とする処理として、繰り返し(ループ)処理があります。同じ作業を何度も繰り返すことは人の作業でもよくあることですが、コンピュータなら、速く失敗なく何度も繰り返すことができます。

for文


繰り返し処理は、条件分岐とジャンプの組み合わせで記述することもできますが、上述の様に重要かつよく使われる機能なので、特別な記述方法が用意されています。一番よく使用されるのが、for文で、以下のような文法になります。

    for (初期値設定式 ; 繰り返し条件式 ; 繰り返し付加式)
        処理



初期値設定式は、繰り返しの前に実行される式です。繰り返し条件式の値が真(0以外)である間、処理を繰り返します。処理の最後には繰り返し付加式が実行されます。繰り返し付加式では基本的には繰り返し条件に関する更新内容のみ記述します。

例えば、以下のように掛けば、10回"Hello World¥n"を表示することになります。

        int i;

        for (i = 0; i < 10; i++) {
                printf("Hello World¥n");
        }

iという短い変数名が使用されていますが、これはiterator(イテレータ:反復子)のiの意味があり、非常によく使われます。続きでj, kと使われることも多いです。

まずi = 0;が実行されます。そしてi < 10が評価され、評価値は真であるため、printf()が実行され、その後i++が行われます。そしてi < 10の評価に戻り繰り返します。

10回繰り返すには、(i = 1; i <=10; i++)の方が分かりやすいと言う方もいると思いますが、上記のように0で初め、終わりの値は10を含めない書き方が普通です。配列の操作などにおいて、コンピュータではそれが便利だからです。

処理においては、break文とcontinue文を書くことが可能です。break文は、繰り返しの処理をそこで終了し、for文の次の文にジャンプします。continue文は繰り返しの処理をそこで終了し、繰り返し付加式にジャンプします。

break文もcontinue文もジャンプというgoto文と同じ性質から構造化プログラミングの観点でふさわしくないとルールで禁止される場合があります。しかし個人的には禁止は反対です。無理に禁止することで返ってネストが深くなりプログラムが見にくくなったり、処理がわかりにくなったりすることも多いと思います。

少し条件分岐に比べて複雑なので、フローチャートで書くと以下のような処理に流れとなります。


while文


for文の初期値設定式繰り返し付加式を取り除いた文法です。既に初期値が設定されており、記述が必要ない場合で、よく使われます。

    while (繰り返し条件式)
        処理


do while文


for文やwhile文においては、繰り返し条件式が繰り返しを行う前に必ず評価されます。その評価が必要なく、処理を必ず一回は行いたい場合に使用します。あまり使うことはないです。

    do
        処理
    while (繰り返し条件式);


無限ループ


初期値設定式繰り返し条件式繰り返し付加式は記述をしなくても良いです。繰り返し条件式は記述しなかった場合は1と等価であり、常に真になるので、無限に繰り返し処理を行うことになります。

繰り返し条件式が複雑でbreak文で終了したり、 他の並列処理から終了したりする場合に使われることがあります。while文でもfor文でも良いですが、for文を使用して、for( ; ; )と書くのが一般的のようです。


配列のループ(C言語)


ループを一番よく使用するケースとして、複数のデータの集合である「配列」の処理を行うことが多いです。配列についてはまだ説明をしていないのですが、色々な種類があります。まずはC言語の場合です。

#include <stdio.h>

int main()
{
        int arr[] = {1, 12, 123};
        int i;

        for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
                printf("%d¥n", arr[i]);
        };
        return 0;
}

sizeof()は演算子でデータのバイト数を取得します。配列のサイズを取得するのに、配列全体のサイズから配列一つのサイズを除算して求めています。環境によってはマクロが用意されている場合があります(LinuxカーネルソースではARRAY_SIZE()マクロ、Windowsでは_ARRAYSIZE()マクロなど)。

配列のループ(C++98)


C++では、ライブラリで定義されたstd::vector等の配列のデータ型があります。これらのデータ型の場合は、メンバ関数としてbegin()関数とend()関数が用意されているため、それらを使用して、開始と終了の値を取得します。取得されたデータ型は〜::iterator型となります(他にもconst_iteratorやreverse_iterator等の型もあります)。iterator型の値はポインタ(これもいつか説明する)の様に*を先頭につけることで配列の値となります。

#include <stdio.h>
#include <vector>

int main()
{
        std::vector<int> arr;

        arr.push_back(1);
        arr.push_back(12);
        arr.push_back(123);

        for (std::vector<int>::iterator i = arr.begin(); i < arr.end(); i++) {
                printf("%d¥n", *i);
        }
        return 0;
}

その他にもC++では配列のループ処理を助ける関数として<algolithm>にfor_each()関数が定義されたりしています。


配列のループ(C++11)


C++にもバージョンが色々とありますが、1998年に出来たC++98に比べて、2011年に出来たC++11では配列の操作関数が色々と強化されています。

例として、以下のようになります。

#include <stdio.h>
#include <iterator>
#include <vector>

int main()
{
        int arr[] = {1, 12, 123};
        std::vector<int> arr2 = {2, 23, 234};
        int i;

        for (auto i = std::begin(arr); i < std::end(arr); i++) {
                printf("%d¥n", *i);
        }
        for (auto i = std::begin(arr2); i < std::end(arr2); i++) {
                printf("%d¥n", *i);
        }
        for (auto i : arr2) {
                printf("%d¥n", i);
        }
        return 0;
}


まず、メンバ関数のbegin()やend()ではなく、通常の関数のstd::begin()やstd::end()関数が<iterator>に用意され、その使用が奨励されています。一番目のループと2番目のループのようにstd::vectorなどの定義型だけでなく、通常のint型にも使えるのが利点です。

autoは直接ループとは関係無いですが、型推論と呼ばれるもので、型を自動的に判別する仕組みです。std::vector<int>::iteratorなどと長い記述をする必要がなくなりましたので、かなり楽になります。

また、std::vector等において、{1, 2, 3}の記述で値を代入する初期化リストにも対応し、int型等の通常の配列と同様に初期化ができるようになっています。

そして、新しい構文としてbegin()やend()を使用せずに配列内全ての処理を行う以下の構文が追加されました。

    for( : 配列)
        処理


再帰


関数から同じ関数を呼び出すことで、繰り返しを行うこともできます。これを再帰と言います。以前に階乗の処理を紹介しました。このときは、for文を使っていましたが、以下のようにfor文を使わず、再帰で表現することも可能です。

下記が再帰を使った例です。do_fact1()関数もdo_fact2()関数も階乗を求める関数ですが、do_fact1()関数はfor文を使用したもので、do_fact2()関数が再帰を使ったものです。

#include <stdio.h>
#include <stdlib.h>

int do_fact1(int value)
{
        int fact, i;

        fact = 1;
        for (i = 1; i <= value; i++) {
                fact = fact * i;
        }
        return fact;
}

int do_fact2(int value)
{
        return value <= 0 ? 1 : value * do_fact2(value - 1);
}

int main(int argc, char *argv[])
{
        int value;

        if (argc != 2) {
                printf("argument error¥n");
                return 1;
        }

        value = atoi(argv[1]);

        if (value &lt 0) {
                printf("value error¥n");
                return 2;
        }

        printf("factorial1 = %d¥n", do_fact1(value));
        printf("factorial2 = %d¥n", do_fact2(value));

        return 0;
}


do_fact2()では階乗の計算が

fact(n) = n * fact(n - 1)
fact(0) = 1

という式で表されることから、do_fact2(value)関数の中で引数を変えて、do_fact2(value -1)関数を呼んでいます。これが再帰です。

再帰を使った繰り返しの利点とては以下があります。
  • 言語としてforの機能が無くても使用できる
  • ハノイの塔やクイックソートなど、再帰でないと記述が難しいアルゴリズムを書ける
一つ目ですが、C++ではテンプレートという機能を使って静的(コンパイル時)に階乗の計算を行うことができますが、このテンプレートにはforの機能がないです。そのため再帰を利用して以下のように書きます。

#include <stdio.h>

template<int N>
class Fact {
public:
        enum { value = N * Fact::value };
};

template<>
class Fact<0> {
public:
        enum { value = 1 };
};

int main()
{
        printf("%d\n", Fact<5>::value);
}

欠点としては以下があります。
  • メモリの使用量が多く、使用量の推定が難しい
関数の呼出は以前に説明したように、スタックというメモリ領域を使用します。そのため、再帰呼出しが多いとそのメモリ領域を多く使うことになります。メモリの少ない場合やどの程度の関数呼び出しがあるか不明な組み込み系などの場合には再帰を使わないほうがいいでしょう。


2014年4月5日土曜日

C/C++ 処理の流れ その2 条件分岐

ユーザーの入力や、様々な要因によって処理の流れを変えたい場合があります。その場合には条件分岐を使用します。

if文


一番主に使われる条件分岐はif文で使用文法は以下です。まずは一番基本的な文法です。条件式が正しい場合(真,trueの場合)、処理1を行い、条件式が違う場合(偽,falseの場合)に処理2を行います。


    if (条件式)
        処理1
    else
        処理2

基本的な文法からelseを省いた形です。条件式の正しい場合に処理1を行います。

    if (条件式)
        処理1

この形はよく使うのですが、安易に使用すると問題になることが多いです。elseの偽の場合の処理が本当に不要か考えたほうが良いです。

条件式の書き方


C/C++言語では0が偽で、0以外が真と見なされます。条件式は比較演算以外でも、どんな演算式でも許されます(例えばif (1)のように数値のみでも大丈夫です)。そのため、以下の2つは間違えやすいので注意が必要です。

    if (a = 10) {
        :
    }

    if (a == 10) {
        :
    }

上側の"="はaに10を代入する演算です。代入演算の結果は代入された値のため演算結果は10です。そのため、このif文の処理は必ず真で実行されます。

下側の"=="はaと10を比較する演算です。比較演算の結果はaの値が10であれば1であり、10でなければ0となります。そのため、このif文の処理はaの値が10であれば真の処理を実行し、10でなければ偽(else)の処理を実行します。

通常は下側の比較演算子を使いますが、間違えないようにするため、以下のように右辺と左辺を逆にする記述を薦める場合もあります(代入演算の場合はエラーになる)。

    if (10 == a) {
        :
    }

しかし、最近のコンパイラでは警告が表示されますし、感覚的にはわかりにくいと思うため、私はお薦めしません。

また条件式には関数を入れることもできますが、&&演算など演算子によっては、関数が実行されないこともあるので、注意が必要です。

処理の書き方


処理の書き方としては分岐後の処理は段を下げて書くのが一般的ですが、段を下げることで処理が変わるわけではありません。それとif分の後に書ける処理は1つのみです。複数の処理を行いたい場合は{}で処理を囲みます。

色々な書き方がありますが、以下のA, B, C, D, E, F, Gの処理を見てください。

    /* A */
    if (a == 10) {
        printf("Hello");
    }
    printf("World");

    /* B */
    if (a == 10)
    {
        printf("Hello");
    }
    printf("World");

    /* C */
    if (a == 10) ;
    { 
        printf("Hello");
    }    
    printf("World");

    /* D */
    if (a == 10) {
        printf("Hello");
    printf("World");
    }

    /* E */
    if (a == 10) 
        printf("Hello");
    printf("World");

    /* F */
    if (a == 10) 
        printf("Hello");
        printf("World");

    /* G */
    if (a == 10) printf("Hello");
    printf("World");

この中で、CとDのみ動作が違い、後のA, B, E, F, Gは同じ動作をします。

Cは条件式の後ろにセミコロン(;)が付いているので、そこで条件が真の時の処理の記述が終了してしまい、"Hello"と"World"は常に表示されます。

Dは条件式が真の時だけ"Hello"と"World"がされます。

その他は条件式が真のときに"Hello"が表示され、"World"は常に表示されます。

Eの書き方は、処理の見やすさとしては問題ないのですが、Fの書き方の問題を起こしやすく、Appleのgotofail問題など、大きな問題となったことがあります。そのため、Eの書き方よりもAのように1つの処理でも{}を使用するAの書き方が奨励されます。

Bの書き方はAの書き方より空白が多く見やすいですが、Cの間違いが起きることがあるため、Aの方が良いという意見があります。

Gの書き方は横に伸びすぎたり、処理部が見にくくなる問題があるかもしれません。

複数の条件式と複雑度


プログラムが大きくなってくると、分岐処理がどんどん多くなっていき、条件分岐の中でさらに条件分岐という形になっています。

    if (条件式1) {
       :
        if (条件式2){
  :
            if (条件式3) {

このようになってくると、プログラムが見にくくなり、複雑度がまし、不具合が多くなることになります。条件分岐は不具合が最も多く出る所と言ってもよいでしょう。

そのために条件分岐を少しでも減らす工夫が必要です。

まず、書き方の工夫ですが、条件により処理を切り替える場合のif文の書き方としては、以下のような書き方があります。

    if (条件式1)
        処理1
    else if (条件式2)
        処理2
    else if (条件式3)
        処理3
         :
         :
    else
    どの条件にも当てはまらない場合の処理

この書き方であれば、ネストが深くなることもありません。

switch文


前述のような else ifのような複数の処理の分岐において、条件式が単純な変数と整数値の一致比較であれば、switch文を使うことができます。switch文の文法は以下です。

    switch (整数の変数){
    case 整数値1:
        処理1
        break;
    case 整数値2:
        処理2
        break;
    case 整数値3:
        処理3
        braek;
         :
         :
    default:
        どの整数値も一致しなかった場合の処理
        break;
    }

整数の変数と整数値1が一致したときに、その処理1が行われます。 break;という行でbreak文がありますが、これはswich文を抜けることを意味します。break文がない場合は、次のcase文の処理も行われます。例えば以下のように、複数の値に一致した場合の処理が同じ場合にbreak文を書かない書き方が使用できます。

    switch (整数の変数){
    case 整数値1:
    case 整数値2:
    case 整数値3:
        処理1,2,3
        braek;
         :
         :
    default:
        どの整数値も一致しなかった場合の処理
        break;
    }

しかし、break文を記述し忘れて問題になることもありますので、注意が必要です。

またdefault:は無くてもエラーにはなりません。しかし、if文においてelseを考えないと問題を起こすことがあるのと同様に、条件に合わなかった部分を考えることは重要ですので、defaultは必ず書くようにした方が良いです。

言語によっては、整数値以外がswitch文で使用できますが、C/C++言語では整数値のみです。


その他の条件分岐 三項演算子を利用する


if文はC/C++言語では文となっていますが、他のプログラミング言語(特に関数型言語と言われる言語)では、式となっていて、条件分岐後の値を返す場合もあります。

C/C++言語でもそのような使い方はできて、三項演算子という演算子があり、以下のような文法です。

    条件式 ? 値1 : 値2

条件式が真の場合には値1を返し、条件式が偽の場合には値2を返します。例えば、以下の2つは同じ意味になります。

    if (a == 0) {
        b = 10;
    } else {
        b = -10;
    }

    b = (a == 0 ? 10 : -10);

if文のように条件式を必ず()で括る必要がないので、注意が必要です。三項演算子全体を()で括っていますが、これは見やすさのためです。

たまに三項演算子の使用をルールで禁止するとこもあるようですが、式であることから色々な使い方ができ、場合によってはプログラムをより簡潔に見やすくすることができます。例えば、以下のような書き方もできます。

    printf(a == 0 ? "Hello" : "World"); 

aが0の場合に"Hello"を表示し、0でない場合に"World"を表示します。

その他の条件分岐 論理演算子(使用しないほうがいい)


論理演算子を使って、条件分岐を行うことも可能です。Perlと言う言語で使われているのを見たことがありますが、C/C++言語では見たことがないです。使わない方がいいでしょう。

    a == 0 || printf("Hello");
    a == 0 && printf("World"); 

上の文は、a == 0が偽のときだけ、||の後のprintfの処理が実行されます。下の文はa == 0が真のときだけ、&&の後のprintfの処理が実行されます。論理演算子の動作を利用したものです。

使うつもりは無くても、条件式の中で使ってしまうこともあるので注意してください。


その他の条件分岐 配列を利用する


配列とはデータの集合のことです。例えば、0〜6の数値を得て、その数値に対する曜日の文字列を返す関数get_week()を作成するとします。

その場合、以下のようにif文を使って書くことができます(さぼって、水曜日までにしています)。

const char *get_week(int num)
{
        if (num == 0) {
                return "Monday";
        } else if (num == 1) {
                return "Tuesday";
        } else if (num == 2) {
                return "Wednesday";
        } else {
                return "";
        }
}

配列を使うと、以下の様に書くこともできます。

const char *get_week(int num)
{
        static const char week[][10]= {"Monday", "Tuesday", "Wednesday" };

        if (num < 0 || num > 2) {
                return "";
        }

        return week[num];
}

この配列を使うということには色々な利点があります。
  1. コード量を少なく、見やすくすることができる。
  2. 関数の配列にすれば処理も自在に変更することができる。
  3. 配列の中身を変更することで、分岐処理自体を変更することができる。 
常に配列が良いとは限りませんが、配列を利用できることは案外と多く、条件分岐を使用せずに、配列で代用できないかを考えることができるようになれると良いのではないかと思います。

その他の条件分岐 多態性を利用する


多態性とは、オブジェクト指向言語などにおいて、プログラミングの型に持たせる性質のことです。ポリモーフィズムや、ポルモルフィズムなど色々と呼ばれています。型によって異なった動作しますので、条件分岐の代わりとして使うことができます。

あまり良い例ではありませんが、以下がC++での多態性の例です。

#include <stdio.h>
#include <stdlib.h>

class Iclass {
public:
        virtual void print() const = 0;
};

class Aclass : public Iclass
{
public:
        void print() const {
                printf("Hello");
        }
        int test;
};

class Bclass : public Iclass
{
public:
        void print() const {
                printf("World");
        }
        int test;
};

void time3(const Iclass &ic)
{
        ic.print();
        ic.print();
        ic.print();
}

int main(int argc, char *argv[])
{
        if (argc != 2) {
                return 1;
        }

        if (atoi(argv[1]) == 0) {
                time3(Aclass());
        } else {
                time3(Bclass());
        }
        return 0;
}


time3()関数で、3回print()の関数を呼び出していますが、このとき引数の型がAclassかBclassかで、time3()関数で表示される文字列が"Hello"か"World"かが変わります。

time3()関数内はif文が使われていないことに着目してください。つまり3回print()を実行していますが、3回のif文が必要無いのです。このように分岐を使わず、型によって処理を分けることで、if文の量を減らすことができます。

上記はC++言語ですが、C言語でも関数ポインタなどを利用すれば多態性と同じことをすることができます。

オブジェクト指向言語では、よいプログラムの書き方をパターン化したデザインパターンという設計手法がありますが、これらでは多態性や配列を使用することによってif文があまり無いようになっています。