2013年11月17日日曜日

C/C++ 数値と名前と変数

動画や画像に音楽などコンピュータは色々なことができますが、
コンピュータの基本は電気が流れる流れないの0と1の世界です。

0と1だけで表した数値を2進数と言います。通常私達がつかっているのは10進数で、

 1
 2
 3
 4
 5
 6
 7
 8
 9
10

の十個目で二桁になります。2進数の場合は

 1
10

と2個目で二桁になります。これらを10進数に割り当てれば数値が扱えることになります。 括弧内は2進数を10進数に変換するための計算式です。2の4乗とは 2×2×2×2→8です。

  1 → 1   (                            2の0乗 × 1)
 10 → 2   (              2の1乗 × 1 + 2の0乗 × 0)
 11 → 3   (              2の1乗 × 1 + 2の0乗 × 1)
100 → 4   (2の2乗 × 1 + 2の1乗 × 0 + 2の0乗 × 0)
   :
   :
10100 → 20 (2の4乗 × 1 + 2の3乗 × 0 + 2の2乗 × 1 + 2の1乗 × 0 + 2の0乗 × 0)

2進数の場合は、桁数が多くなりすぎますが、 10進数と2進数では桁上げのタイミングが違いすぎるので、 8進数や16進数が使われます。特に16進数がよく使われます。


10進数2進数8進数16進数
0000
1111
21022
31133
410044
510155
611066
711177
81000108
91001119
10101012A
11101113B
12110014C
13110115D
14111016E
15111117F
16100002010
17100012111
::::


そして2進数の1つの1と0のデータの単位をビット(bit)、16進数で2桁(2進数で8桁)の0〜FF(0〜255)の単位をバイト(byte)と呼びます。

プログラムで計算などを行うには、メモリを使いますが、コンピュータとしてはメモリも100番地と150番地のデータのように数値で区別して扱います。通常の場合は1つの番地に1バイトのデータを記憶できます。

しかし、こんな数値ばかりでメモリを管理していたら大変です。ソースコードでは大抵、メモリを管理するためには自分で名前を付けた「変数」を使用します。

階乗の計算のソースコードでは、value, fact, iが変数です。自由に名前を付けれますが、英字の大文字小文字、数字、アンダーバー(_)が使用できます。先頭に数字は使用できません。せっかく自由なので分かりやすい名前にしましょう。iは分かりにくく感じると思いますが、繰り返し処理に使用される因子(iterator)の意味から来ているもので、よく使われる定石みたいな物となっています。

この変数の前についているintは「型」といって、データの意味を表すものとなっています。main()内の先頭行ではvalue, fact, iがint型であることを宣言しています。

最近のPCでは、intは32bit(4バイト)で10進数だと−2,147,483,648 から 2,147,483,647の値のデータと言う意味です。計算結果がこの範囲を越えることをオーバーフローといい、計算結果が意図通りにならない原因の1つです。

小数点を扱いや、マイナスの扱いも2進数で扱います。どのように扱うかは、IEEE754や、2の補数を調べてみてください。

2013年11月10日日曜日

C/C++ 階乗の計算

コンピュータと言えば計算が得意です。ですから計算のプログラムを作ってみます。計算は階乗(factorial)の計算で、nという数字を与えた時に、1×2×3×4×・・・・×(n-1)×nという1からnまでを掛けた値になる計算です。なお0の階乗は1となります。

まずはどのように動くかという仕様を決めます。これだけのプログラムでも色々と考える必要があります。
  1. コマンドライン引数で階乗の数値を入力する。
  2. コマンドライン引数で入力した数値から階乗を計算する。
  3. 計算した結果をコンソールに表示する。
  4. 引数では数値を入力する。数値以外は0と見なす。
  5. 数値がマイナスの場合はエラーを表示し、2を返す。
  6. 小数点がついていた場合は、小数点以下を無視する。
  7. 引数の数が1つでない場合はエラーを表示し、1を返す。
  8. 値のオーバーフローは考慮しない。
上記のように動くソースコードを書きます。fact.cとします。


コンパイルし、実行を行うと以下のようになります。



ソースコードの解説を簡単にします。

ソース説明
main(int argc,  char *argv[])今回はmain()関数にint argc, char *argv[]という2つの引数がついています。argcは整数(int)で、引数の数を表します。argvは引数の文字列の配列を意味します。*や[]の意味はまた機会があれば説明します。とりあえずargv[1]などが一つ目の引数の文字列と解釈してください。
int value, fact, i;valueと、factと、iという名前の整数のデータ領域を作成しています。
if (argc != 2) {...}引数の数(argc)のチェックを行っています。引数の数にはコマンド自身の数も含まれるので引数が1つのときには2となります。!=2はargcが2で無いかどうかという計算で、その結果により、{...}内の処理を行います。

value = atoi(argv[1]);
引数の文字列argv[1]を整数値に変換して、valueに代入しています。文字列のままでは計算ができないためです。atoi()というのは文字列を整数値に変換するライブラリ関数です。stdlib.hに入っているのでstdlib.hを#includeしています。
if (value < 0) {...}引数を整数値に変換したvalueが0より小さい場合に{...}の処理をします。
fact = 1;factというデータ領域に1を代入します。
for (i = 1; i <= value; i++) {...}繰り返し処理を行い、階乗を求めています。(1)iのデータ領域に1を代入します。(2)iがvalue以下の値か調べ、iがvalue以下の値であれば、{...}の処理を実行し、そうでなければ{...}の次の行の処理に移ります。(3){...}内の処理が終わったらi++を実行します。i++はiの値をi = i+1と同じ意味でiの値に1つ値を足します。
fact = fact * i;アスタリスク(*)は掛け算の意味です。iが1つずつ加算するのを繰り返すことで、階乗を計算します。
printf("factorial = %d¥n", fact);
階乗の値を出力します。printf()で表示する文字列の指定内のパーセント(%)には意味があり、%dは整数(int)の表示を行うことを意味します。文字列の後ろの引数に%dに表示するデータを列挙します。

複雑になりましたが、それは今回のプログラムがユーザーからのコマンドラインからの入力が増えたことが原因です。これでもオーバーフローを考慮しないなど、処理が少なくなるようにサボっています(17の階乗でなるはずの無いマイナス値となっているのはオーバーフローのためです)。

通常の場合、プログラムはユーザーの入力に対し、何かの処理を行います。ユーザーの入力は何があるか分からないので、入力を制限する処理を入れたり、どのような入力でもチェックによりエラーを返すなどの処理を入れなくてはいけません。そうしないと 想定外の問題が起こることになります。

この想定外の問題をいかにして少なくすることができるかが、プログラマの腕の見せ所の1つです。

それと、今回はCPUの処理(演算と制御)の部分とメモリ(データ領域の作成)の部分がHello Worldに比べて多く増えています。それらについてはまた次回。

2013年11月4日月曜日

MinGW + MSYS で日本語

C/C++ Hello Worldでは、わざと日本語の使用を避けていました。日本語を扱うには色々と問題があり、設定などが難しかったためです。

gccのコンパイルで警告になることがある

printf("Hello World¥n");でなく、 printf("こんにちは¥n”);とした場合は問題になりませんが、printf("ソフトウェア¥n”);とした場合にはコンパイル時に警告が表示されます。

なぜかというと円マーク(¥)が原因です。円マークは文字コードが0x5Cなのですが(Linuxでは0x5Cの表示がバックスラッシュ(\)の場合もあります)、Windowsが標準で使用している文字コードのCP932(ShiftJISの一種)では、「ソ」は0x83,0x5Cの2バイトとなっていて、コンパイラが2つ目の0x5Cを円マーク(¥)と間違うためです。他にも「表」などが同様に問題があります。


その対策では、--input-chaset=CP932というオプションを付けます。するとgccは文字コードをUTF-8 に変換してからコンパイルします。しかし、これだけではprintf()で文字化けします。--exec-charset=CP932とすることで、実行ファイル内部の文字列をCP932に変換します。



そもそもvimで日本語入力ができない

まずはvimで日本語が化けます。この対策で、.vimrcの最後の行に
set enc=japan
と入れると良いようです。ここを参照させていただきました。

しかし、日本語入力はうまく行きません。Alt+半角/全角で日本語入力への切換ができるのですが、vimの画面では変な風にスクロールしていってしまいます。
この対策にはminttyを使うと良いようです。以下でセットアップできます。

$ mingw-get install msys-mintty

"mintty -"(ハイフンが必要)を実行するか、msys.batを起動するときに -minttyオプションを付けるとminttyがが起動します。mintty上で起動したvimであれば日本語入力が出来ます。ここを参照させていただきました。以下はminttyの画面です。



さらに日本語ファイル名を使う

上記ではlsで日本語のファイル名がまた文字化けしますし、入力もできません。以下を行うと日本語ファイル名が使えるようになります。

/etc/profileのファイルを編集して最後に以下を追加します。
alias ls='ls --show-control-char'

また、~/.inputrcを編集して16行目以降を以下のように変更します。
# disable/enable 8bit input
set meta-flag on
set input-meta on
set output-meta on
set convert-meta off

同じくここを参照させていただきました。

MinGW + MSYS で GTK+3

GTK+のバージョン3はなかなかWindows版が出なかったのがやっと出たようです。インストール方法も載っていますが、MSYSで使うのであれば、より簡単かもしれません。

all-in-on bundleをダウンロードして、C:¥MinGW¥msys¥1.0に解凍して上書きします。

その後、MSYS上で以下のコマンドを実行します。
$ pango-querymodules > \etc\pango\pango.modules
$ gdk-pixbuf-query-loaders > \lib\gdk-pixbuf-2.0\2.10.0\loaders.cache
$ gtk-query-immodules-3.0 > \lib\gtk-3.0\3.0.0\immodules.cache

たぶん、これでいいはず・・・
コンパイル方法も全く一緒で、これと同じプログラムが動きます(-mwindowsオプションは付けたほうが良いかもしれませんが)。

C/C++ Hello World ウィンドウ表示(GTK+)

Linuxにおいてもウィンドウ表示のHello Worldを作成してみます。

Linuxでウィンドウアプリケーションを作るにはツールキットと言われるソフトを使用します。GTK+や、Qt(キュート)が有名です。GTK+は少々使いにくいかもしれませんが、C++でなくC言語から使えるのが特徴です。

まずは、GTK+の開発環境をインストールします。

Fedoraの場合
$ sudo yum install gtk3-devel

Ubuntuの場合
$ sudo apt-get install libgtk-3-dev

hello2.cは以下のように作成します。
#include <gtk/gtk.h>

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

        gtk_init(&argc, &argv);
        dialog = gtk_message_dialog_new(NULL, 
                                GTK_DIALOG_DESTROY_WITH_PARENT, 
                                GTK_MESSAGE_OTHER,
                                GTK_BUTTONS_OK,
                                "Hello!"
                                );
        gtk_dialog_run(GTK_DIALOG(dialog));
        gtk_widget_destroy(dialog);
        return 0;
}

コンパイルを以下のように行い、実行します。
$ gcc hello2.c `pkg-config --cflags --libs gtk+-3.0`
$ ./a.out

すると以下のようになウィンドウが表示されます。


少し難しいですが、簡単に各項目の説明をします。
項目説明
mainmain()関数はargc, argv, envの3つの引数を持つことができます。今までは全て省略していました。envのみ省略もできます。
int argcコマンドライン引数の数が渡されます。
char *argv[];コマンドライン引数の文字列の配列が渡されます。
GtkWidget *dialog;dialogという名前のウィンドウをこれから作ることを宣言します。
gtk_init()GTK+を使用する前に必須の初期化を行います。
gtk_message_dialog_new()メッセージを表示するだけの簡単なウィンドウdialogを作成します。一つ目の引数は、親ウィンドウを指定します。二つ目の引数は動作を指定します。三つ目の引数はどのようなアイコンを出すかを指定します。4つ目の引数はOKボタンを出す事をしていします。5つ目の引数は、表示する文字列です。
gtk_dialog_run()dialogを表示し、OKが押されるまで待ちます。
gtk_widget_destroy()作成したdialogを削除します。

コンパイル時には`pkg-config --cflags --libs gtk+-3.0`を指定します。「`」は日本語キーボードでは「@」のキーをShiftキーを押しながら入力すると表示される文字です。これを指定するとpkg-configのコマンドの表示結果をそのままコマンドに渡すことができます。pkg-configは、GTK+で必要なヘッダーファイルの場所と、ライブラリを指定する引数を表示するコマンドです。

GTK+の詳細についてはここにリファレンスマニュアルがあります。ただし全部英語です。

C/C++ Hello World ウィンドウ表示(Windows)

コンソールにHello Worldを表示するプログラムを作りましたが、ウィンドウに表示をした方がプログラムを作った気になるかもしれません。そこでウィンドウにHello Worldを表示するプログラムの作成です。

Windowsの場合、多くのライブラリがMicrosoftから提供されており、Win32 API、.NET Framework API、WinRT API、MFC、ATLなど色々とあります。この中で最も原始的なWin32 APIがC言語と相性がいいので、それを使用します。

プログラムは以下です。



以下のようにコンパイルし、実行します。
$ gcc hello2.c -mwindows
$ ./a.exe
すると以下のようなウィンドウが表示されます。


ソースコードはコンソール版とかなり違います。違いを簡単にまとめます。
コンソール版 ウィンドウ版 説明
#include<stdio.h> #include<windows.h> Win32 APIの使用のために、windows.hを組み込みます。
int main() int _tWinMain() ウィンドウでは、最初に実行される関数がmain()では無いです。WinMain()またはwWinMain()となります。一つ目と二つ目の引数は過去に使用していましたが、今は使用していません。 三つ目の引数はコマンドライン引数です。四つ目はウィンドウの表示状態です。
printf() MessageBox() MessageBoxは単純なウィンドウを表示するWin32 APIの関数です。一つ目の引数は親ウィンドウで今回は無いのでNULLです。二つ目の引数はウィンドウ内に表示する文字列、三つめの引数はタイトルの文字列、四つ目の引数はOKボタンを出す事を指定しています。

Win32 APIには、ANSI版とUNICODE版の二つの関数があります。#include<tchar.h>が、その二つの関数の切り替えの仕組みの一つだと考えてください。_T()によって文字列をANSIとUNICODEで切り替えます。_tWinMain()もANSI版のWinMain()とwWinMain()に切り換えられ、MessageBox()もANSI版のMessageBoxA()とMessageBoxW()に切り換えられます。面倒ですが切り替えが出来るように作成した方が将来的に良いかと思われます。

コンパイル時には、-mwindowsというオプションを付けています。これはWindowsの実行形式にはコンソール版とウィンドウ版の二通りがあり、このオプションを付けないとコンソール版となってしまうからです。コンソール版では実行時に必ずコンソールを作成します。エクスプローラからa.exeを実行すると違いがよくわかると思います。

Win32 APIはウィンドウ関係だけでなく、Windowsのほとんどの機能が提供されています。MSDN LibraryのサイトにおいてWindows開発→Windows 2000と辿った所にドキュメントがあるため、Win32 APIの詳細はそれを参照してください。

2013年11月3日日曜日

C/C++ Hello World が動く仕組み

Hello Worldのプログラムを作って動かしたのですが、これがどうやって動いているかを説明します。

まずは、コンピュータの仕組みから説明します。コンピュータは基本的に次の3つから出来ています。


メモリ ↔ CPU ↔ I/O(Input/Output)


キーボードやマウス、ディスプレイにHDDはどこか?というとI/Oにつながっています。

そして、ご存知のようにコンピュータは電気で動いています。電気が流れる・流れないの2つの状態を大量にメモリに持っています。CPUでその電気の流れる・流れないの状態を取得したり、変更したりします。またCPUはキーボード等からI/Oを通じて電気の流れる・流れないを取得したり、ディスプレイに電気の流れる、流れないを渡して表示を行ったりすることができます。

単純に流れる・流れないは 2値(ディジタル)ですが、2値をたくさん組み合わたデータを操作することで色々なことができるのがコンピュータです。



さて、Hello Worldの仕組みに戻ります。a.exe(a.out)というファイルが作られたのですが、このファイルは実行形式のデータとなっています。画像は単に画像形式に、文書は文書形式になっているだけで、コンピュータは2値を組み合わせたデータを扱うと言う点に違いはないです。

実行形式のデータはOSによって中身が違っており、WindowsではPE(Portable Executable)という形式になっており、LinuxではELF(Excutable and Linkable Format)という形式になっています。gccなどのコンパイラはコンパイルすることでこれらの形式のファイルを作ります。

実行形式のファイルを実行する操作を行うと、OSが実行形式をI/Oを通じてHDDからファイルをメモリ上に展開します。 そして、main()という名前の関数から実行を行うようになっています。

CPUの部分では、以下の手順で実行を行っています。

printf();の実行
 ↓
return 0; の実行

メモリの部分では、プログラム自身と"Hello World\nGood bye\n"などの文字列を記憶しています。

I/Oの部分はプログラムを書くのが難しい部分です。そこで他の人(OS提供元など)が作ったプログラムを組み込み、それを使います。その組み込むプログラムのことをライブラリと言い、ヘッダーファイル(○○.h)と一緒に提供されます。

#include<stdio.h> では、標準I/O(Standard I/O)と言うものを組み込んであり、コンソールに表示を行う機能など、WindowsでもLinuxでも使えるような機能が提供されています。

ライブラリの組み込みは通常は、コンパイル時のオプションで指定する必要があります。しかしC言語の標準的な機能は、何も指定しないでもデフォルトで組み込まれるようになっています。

 以下のように"-v"オプションを付けてコンパイルを行うとコンパイルで何を行っているかの詳細が表示されます。

$ gcc -v hello.c

最後の辺りで -lmsvcrt などの文字が出てくると思います。これらがデフォルトで組み込まれたライブラリです(-lがライブラリを指定するオプションでmsvcrtというライブラリを組み込んだと言うことになります)。

Hello Worldの仕組みについては以上です。Hello Worldでも、大きいプログラムでも、コンピュータの基本が「CPU」、「メモリ」、「I/O」であることから、これらを操作する処理を書くことがプログラムを書くこと、になります。