C言語によるプログラミングに関する世界観2(制御フロー)です。
「プログラミング言語C」などを参考にしています。
ツール関係はC/C++ツールに移動しました。
システムに依存する部分はLinux(システムコール・API)やWindowsプログラミングも参照のこと。
C言語のプログラムは、main()関数から実行される。このmain()関数を実行する時に、main()関数の引数を与えることができる。この引数のことを「コマンドライン引数」と呼ぶ。
このコマンドライン引数では、「コマンドラインからプログラムを実行した時に、空白で区切られたコマンドライン引数」が格納される。grep hogeなら、argv[0]に"grep"、argv[1]に"hoge"となる。
具体的には、以下のようにmain()関数を記述する。
int main(int argc, char *argv[]) { ... }
ここで、argcは引数の個数であり、argvは引数の文字列を指すポインタの配列。
注意点としては、「プログラム自身の名前」というものも、この引数の中にカウントされること。argcにはプログラム名も含まれる。argvの最初の要素(argv[0])はプログラム名になる。
また、main()関数がint型なのは、プログラムが正常に終了したかどうかを返り値として返すことがあるからである。正常に終了した場合は、return 0;とする。
if文は条件式に応じてコードの現在位置を分岐したり、「ある条件が満たされる場合にのみ処理を行う」といったことができる。条件が満たされる場合と満たされない場合によってif ~ else文で複数分岐できる。
if (x >= 0) printf("xは正の数か0\n"); else printf("xは負の数\n");
処理が二行以上になる時は{}を使う。
if (x >= 0) { printf("xは正の数か0\n"); } else { printf("xは負の数\n"); }
if ~ else if文によってさまざまな条件を選択することができる。
if (x == 0) { ... } else if (x == 1) { ... }
switch-case文を使うこともできる。変数の値によって別のコードを実行できる。どの値でも無かった時のためにdefaultを最後に実行することが多い。
switch (x) { case 0: ... break; case 1: ... break; case 2: ... break; default: ... break; }
最後にbreak文をつけないと、ひとつのラベルの処理が終わった時点でブロック外に戻らず次のラベルの処理に行ってしまう(フォールスルー)ので、何らかの意図がない限りbreak文をつける。意図的にフォールスルーをしたい場合は、
/* FALLTHROUGH */
のようなコメントをつけることが慣習となっている。
また、enumを用いることで文字列でswitch ~ case文を実行することもできる。
forは繰り返し。ある条件が満たされる間、同じ処理を繰り返すことができる。if文と組み合わせて使うことも多い。
for (i = 0; i < n; i++) ...
forの場合、for ()の括弧内の一番左の
i = 0;
は初期化、
i < n;
は繰り返しの条件文(条件を満たさなくなれば繰り返しをやめる)、
i++
は繰り返しがひとつ終わったらそのたびにすることを示している。
処理が二行以上になる時は{}を使う。
for (i = 0; i < n; i++) { array[i] = 0; }
forの他にwhile文を使うことも多い。while ()の括弧内には条件文だけを記述する。
while (*p != '\0') { ... p++; }
while文では、繰り返しを続けるための条件だけを()内に記述する。そのため、どこかで繰り返しを終えるようにコードを書かなければならない。
for文やwhile文では、コード内にbreak;を記述することで繰り返しを終えることも出来る。またcontinue;を記述することで、次のループを実行することもできる。
これとは違った繰り返し文として、do-while文を使うこともできる。do-while文では、まず最初に文が実行され、その後に条件式を評価し、条件式が真ならば次の繰り返しが実行される。繰り返しの前に文が実行される(do)、という点が違う。
gotoはジャンプ命令で、ラベルのある場所にジャンプする。
gotoを多用すると、分かりづらく保守しづらいプログラムになることが多く、カーニハン&リッチーの「プログラミング言語C」でも推奨されていない。多くは関数と条件分岐で代用できる。だが、以下のように2つ以上のループから抜け出すような場合には、gotoは例外的に有用である。
for (i=0; i<10; i++) { for (j=0; j<10; j++) { printf("%d, %d :", i, j); if (getchar() == 'q') { goto out; } } } out: exit();
また、「その関数が正常終了した時に、さらに処理を続ける」といった場合に、if文の中に関数を入れ子構造にして入れることもある。
if文だけではなくwhile文などでも同じことをする。
X11やWindowsのGUIプログラミングなどでは、メッセージループと呼ばれる、GUIアクションのイベントメッセージをwhileなどのループで回し、イベントに応じてswitch文などでメッセージ処理を行うことがある。
この時、ループの中ではイベントメッセージが何か送られてくるまで待機し、送られてきた状況とそのメッセージに応じてさまざまな処理を振り分ける。
後日注記:ウィンドウシステムに近い低水準のGUIプログラミングでは、メッセージループをよく使います。メッセージループはイベント駆動の一種で、マウスのクリックやキーの入力などがイベントメッセージとして発生した時に、そのイベントをクライアントのインスタンスに渡して、ループ処理でイベントごとに異なる処理を実行します。カーネルのイベント駆動では、システムコールを待機して呼ばれたシステムコールごとに処理を行いますが、メッセージループはGUIプログラミングにおけるイベント駆動であり、「GUIにおける準カーネル的な処理の仕組み」であると言えるかもしれません。
イベント駆動やWindowsプログラミングやX11設定とプログラミングも参照のこと。
2023.02.17編集
グローバル変数とは、関数の中のローカルスコープに定義されたローカル変数ではなく、関数の外のグローバルスコープに定義された、全ての関数からアクセスできる変数のこと。
初心者がやりがちなこととして、グローバル変数とgotoを使うのは良くない。
関数や引数、オブジェクト指向のクラスやインスタンスを使うことで、手続き型すぎるのではなく、オブジェクト指向や関数型プログラミングのようなプログラミングを行うことを心がけよう。
後日注記:グローバル変数を「絶対に使ってはならないわけではない」。たとえば、複数の関数から共有されるデータがあった場合、それをグローバル変数(外部変数とも言う)にすることは、ローカル変数(自動変数とも言う)に宣言できない時は使った方がプログラムがコンパクトになることがある。しかしながら、このような場合、外部変数に対するstatic(静的変数とも言う)を使うことで、「そのソースファイルに限ってアクセスできる」(別のソースファイルからはアクセスできない)ようにすることもできる。
保守性も参照のこと。
C言語でカウントが0から始まる理由は、ポインタを操作する際にメモリアドレスの数が+0から始まるからです。
最初の要素はメモリアドレス+0、次の要素はメモリアドレス+1となるため、カウントは0から始まった方が都合が良いのです。
配列の添え字が0から始まるのも同様の理由です。アドレスのオフセットを0から始めさせることで、*(p + i)はp[i]としてアクセスできます。
もうひとつの理由として、二進数が0000から始まる、というのがある。0001から始まるよりも、0000から始まった方が、コンピュータとして表現・利用しやすいことがある。
ある意味、「C言語ハッカーのジョーク的慣習」のようなところもある。
後日注記:たとえば、コンピュータのメモリの番地は、一番目が00000000、二番目が00000001となる。二進数で表現した際の最初のデータが0で、その次が1になることから、最初の番号は1ではなく0にしたほうが、コンピュータでは扱いやすいのである。
後日注記:効率やデータ量の表現量から見ても、0からインデックスが始まるのは都合がいい。1からインデックスを始めた場合、0から始めるのに比べて、変数として与えられた記憶量あるいは表現できる情報量を1ビット損してしまう。なので、貴重なコンピュータ資源を効率的に使い、できるだけ多くの情報量を表現するために、C言語では添え字やインデックスは0から始まる。
2023.11.17編集
2024.05.22編集
関数には「返り値の型(return文で返される値の型)」と「関数の名前」と「引数(呼び出される時に指定できる変数)」を指定し、
int func(int x) { return x + 10; }
であれば、
y = func(100);
のように呼び出す。
このように、returnを使って返り値を返すことで、実行結果を呼び出し元に返すことができる。C言語でサブルーチンが「関数」と呼ばれるのは、return文があるからである。
計算結果を返す目的だけではなく、特定の処理をサブルーチンにして「汎用的に操作したい」時にも、return文を使うことができる。
たとえば、
#include <stdio.h> int funcA(int x) { return 3 + x * 6; } int funcB(int x) { return 2 + x * 4; } void print_number(int x) { printf("%d\n", x); } int main(int argc, char *argv[]) { int x, y; x = 10; y = funcA(x); print_number(y); y = funcB(x); print_number(y); x = 15; y = funcA(x); print_number(y); y = funcB(x); print_number(y); return 0; }
このように、関数はひとつひとつの計算処理を再利用するために使うことができる。
2023.01.19
returnと引数を上手く使うことで、「自動の実行処理」を上手く作ることができる。後述するポインタを引数の中で使うことで、さまざまなサブルーチンを抽象化されたアルゴリズムとして記述することができる。関数とは自動の実行処理である。特にUNIXのprintf()などの関数では、「ストリーム」という考え方で、まるで「変数が関数の中を通り抜ける」ように関数を記述することができる。
後日注記:バグを無くすという点から見ても、注意深くコアの関数を実装して、あとはその関数を呼び出す形でプログラムを記述することで、「絶対に動く」という信頼性の向上につながる。逆に関数にする必要が無いのに関数をむやみに作っていると、相互依存性の問題から破綻することがある。特に、さまざまな場所に処理が分散すると、いざそれを書き直そうと思った時に保守できないこともあるため、バランスが必要である。
後日注記:C言語に限らず、コンピュータでできることは、事前に書いた処理を自動的に実行することだけです。この「事前の自動処理を書いて実行する」という基本が分かれば、パソコンのことはすべて分かったと言えます。特に、Gentoo Linuxなどの一部のLinuxディストリビューションを触ると、パッケージ管理システムのPortageなどが自動の実行処理を行うため、パソコンが自動の実行処理機械であることがよく分かります。
2023.02.17
同じことは、ポインタを使うことでも実現できる。引数に構造体のポインタを与えて、関数の中でポインタの指す構造体データを操作することが出来る。
特定のデータを参照だけをする場合もあれば、特定のデータを書き換える場合もある。
これによって、複数のデータをひとつの構造にした構造体を操作・処理するたくさんのメソッドを作ることが出来る。
特に、LinuxカーネルやWindowsプログラミングで、構造体のポインタを使う場合が多い。
これはオブジェクト指向にも良く似ていて、Pythonなどでは第一引数に「self」と呼ばれる自分自身を指す参照名を定義し、self.valやself.func()のようにメンバ変数やメソッドにアクセスする。
C言語でオブジェクト指向を実現したい場合は、場合によってGTK+などで使われているGLibというライブラリを使うこともできるし、C++で書くこともできる。
後日注記:Cでは、FORTRANなどと違い、関数を呼び出した時に引数が「参照」ではなく「複製」として扱われる。このため、関数の内から外を操作することができない。しかしながら、swap(&x, &y)などと関数にアドレスを与え、その関数をvoid swap(int *p1, int *p2) { ~ }とすることで、関数の内部からポインタで外部の変数を操作する(この場合ではxとyの値を入れ替える)ようなことができる。ポインタを使うことで、関数の内から外を操作できる。ほかにも、配列を渡すのであればポインタをインクリメントしてひとつひとつアクセスするかのようなこともできる。
関数は出来るだけ汎用的に作ろう。同じ関数を使いまわしたり、繰り返し使うことが出来たら、それは一段階上のプログラミングが出来ている。
後日注記:関数の基本は使いまわすことです。さまざまな場所から使われる関数のコア部分を、一か所にまとめて記述すること、それがプログラミングの基本です。関数を使いまわす上で、関数型プログラミングの技法として、「関数を関数に渡す」ということができる場合があります。関数のコア部分では具体的な処理を決めず、その状況に応じて関数の具体的な処理を関数に与えるような関数です。C言語は手続き型プログラミング言語ですが、関数ポインタを関数に渡すことで、ある程度の目的は達成できます。ですが、クロージャなどが効果的に使えて、関数を戻り値として返すことができるような、Cよりも高水準のプログラミング言語を使うほうが、そのような関数型プログラミングはやりやすいでしょう。
2023.02.17
関数を使ったやり方として、
1.ひとまとまりのコードブロックに名前をつける。
2.引数を使ってコードブロックの一部分だけの処理を変えて、ほかの処理を使いまわす。
3.繰り返し、どのタイミングからでも関数を呼び出す。たとえばfor文から関数を呼び出したり、if文で関数への引数を変えて呼び出したりする。
4.データ構造を操作するための外部からのインターフェースとなるAPIを実装する。オブジェクト指向と同じことをC言語で行う。たとえばスタックのプッシュとポップを実装する。
5.実装の詳細を抽象化する。関数の呼び出し側が必要なこと以外考えなくていいようにする。open(), close(), read(), write()などが例。
6.汎用的な万能関数を作る。たとえば、ペイントを行うrender()関数を作ってペイントソフトを作る。
7.数値や文字列などのデータに演算や処理を行って、結果を返す。データからデータを返す汎用的な関数をさまざまなところからタイミングに応じて使いまわす。
8.入出力などの処理を行うための、フィルタリング的な「出入口」を作る。たとえば、その関数のみを使って入出力を行ったり、その関数からしかデータを操作できないようにする。たとえば、スタックであれば、直接内部の配列にアクセスするのではなく、専用の関数を用いて間接的にアクセスする。
9.ひとつの処理だけに特化した専用の関数を作る。たとえば二分探索木の構築と探索の専用の関数など。
10.関数という一部分の範囲に処理を限定し、さまざまな関数を組み合わせることで、高度な処理を実現する。
などというやり方があります。
ライブラリとは、それ自体が単独のプログラムとして動作するのではなく、別のプログラムから読み込まれて使われるプログラムのこと。
たとえば、GTKアプリを使う場合、GTKのライブラリを使うことで、自分で作ったプログラムからGTKの機能をライブラリAPIとして使うことができる。
ライブラリは関数やデータ構造の集合体であり、自分で書くこともできるし、GTKのように誰かが作ったライブラリAPIを使うこともできる。
プログラムとライブラリのような共有オブジェクト(ライブラリのバイナリ)を結合することを「リンク」と言う。リンクには、そのまま物理的に結合する「スタティックリンク」と、実行時に動的に結合する「ダイナミックリンク」があり、普通はダイナミックリンクを使う。
Cのライブラリであるlibcはどのようなプログラムでも必ずライブラリとしてリンクされている。
ライブラリを使ってプログラムを書くあるいはビルドするためには、関数のプロトタイプ宣言(関数の名前と引数と戻り値の型の宣言)が記述されたヘッダファイルをincludeで読み込む必要がある。ヘッダファイルは自分で書く場合は拡張子.hファイルをincludeすればいい。誰かが書いたものを使う場合は/usr/includeなどにヘッダファイルを導入する。パッケージ管理システムを使う場合、-devel系のパッケージを導入すればいい。
また、ライブラリとダイナミックリンクされたプログラムを実行するには、ライブラリの共有オブジェクトがシステムの/libあるいは/usr/libディレクトリなどに存在する必要がある。多くの場合パッケージ管理システムで導入すればよく、バイナリパッケージに付属する依存関係などで通常はパッケージをインストールする際に依存ライブラリも自動的にインストールされる。
Linux共有ライブラリも参照のこと。
2023.09.20
ヘッダファイルとは、関数やデータ構造などのプロトタイプ宣言が記述されたファイルのこと。
C言語のファイルがmain.cなど、ひとつだけの場合は、ヘッダファイルがなくてもプログラムは動作するが、ソースファイルが2つ以上になった場合、そのソースファイルから別のソースファイルの関数を呼び出すためには、プロトタイプ宣言をする必要があり、一般的にプロトタイプ宣言だけを記述したヘッダファイルをhoge.hなどとして作成し、このプロトタイプ宣言をすべての*.cファイルから#includeするようにする。このようにすることで、別のファイルで定義された関数を実行することができる。
プログラムが大規模になってくると、ヘッダファイルはひとつではなく、大量に作られ、必要なヘッダファイルだけを#includeで複数読み込むようにすることが多い。
システム標準のパスからstdio.hを#includeするには、#include <stdio.h>とすればよい。だが、自分で作ったヘッダファイルの場合、同じディレクトリから読み込むため、"~"を使って#include "hoge.h"とする。
Linuxヘッダファイルも参照のこと。
また、単独のプログラムではなく、外部からサブルーチンを呼び出して実行する「ライブラリ」と呼ばれる受動的なプログラムを作って使うこともできる。ライブラリを使う時は、使いたい関数の宣言の書かれたヘッダファイルを#includeして読み込む必要がある。ヘッダファイルには、実装とアルゴリズムは書かれていないが、関数の宣言だけが書かれている。たとえば、
int func(int x, int y);
のような形である。printf()やgets()のような入出力関数はシステムのstdio.hファイルに書かれているため、#include <stdio.h>とコードの一番最初に書くことで、コードの中でprintf()やgets()のような関数を使うことができるようになる。「stdio」は「標準入出力(スタンダードIO)」の意味である。
C言語の関数には、「関数を使う前にその関数が記述されていなければならない」というルールがある。だが、先のヘッダファイルには、前述したような「関数のプロトタイプ宣言」が記述されている。関数を使う前の準備として、このプロトタイプ宣言が記述されていれば、C言語ではそれ以降の記述で関数を使うことが出来る。ヘッダファイルは、このプロトタイプ宣言が記述されたファイルである。ヘッダファイルを#includeすることで、関数をそのファイルの中でいつでも使うことが可能になる。
後日注記:関数だけではなく、構造体や自分で作ったデータ型なども、ヘッダファイルに書いて読み込む形式にすることが多い。基本的に、別のファイルに記述された関数や型を実行するためには必ずプロトタイプ宣言が必要。実際には同一ファイルに記述された関数であっても、順番を気にする必要が無いという理由からプロトタイプ宣言を記述したり、ヘッダファイルをincludeすることが多い。
Cで関数やグローバル変数を複数のファイルで共有する場合は、
#includeするヘッダーファイルの.hに
extern int hoge;
と書いて(宣言)、ソースファイルのいずれかに
int hoge;
と書く(定義)。
関数についても同様で、
extern int func(int, int);
のようにプロトタイプ宣言を行う。
オブジェクト指向のようなインターフェースは用意されない。データを破壊しないように気を付けて使おう。
以上は以下のサイトからの引用。
後日注記:宣言はその変数を使う全てのファイルで必要であり、外部のソースファイルの中の変数にアクセスするためにはexternをつけて宣言する。定義はメモリ上にデータが確保されることであり、一度きりで良いのでそのグローバル変数を主に使用するひとつのソースファイルで定義すればいい。
Cの標準ライブラリ。Linuxではglibcが使われるが、Windowsや*BSDなどではそれぞれの実装を利用する。
良く使うのは入出力ライブラリの中のprintf()など。入力ライブラリでは安全性とセキュリティに注意(誤ってポインタに変数の大きさ以上の入力を入れないこと)。
以下のようなサイトから標準Cライブラリのヘッダ一覧を参照できます。
以下の書籍が参考になります。
プログラミング作法(3A.制御フロー)も参照のこと。
構造化プログラミングも参照のこと。
関数も参照のこと。
インターフェースを参照のこと。
関数型プログラミングも参照のこと。