![]() |
zunbe |
![]() |
|
| [<<先頭] [<前へ] [ 1 2 ] | ||
2005/07/16
|
ずんべは、プログラムを書くとき goto をよく使う。
goto は諸悪の根源の様に言われるが、ずんべは goto を積極的に使っている。
たとえば、こんなコードである。
-----
int function(char * fname1, char* fname2)
{
FILE* fp1;
FILE* fp2;
int ret;
// コンストラクト
ret = FALSE;
fp1 = NULL;
fp2 = NULL;
// 本処理
fp1 = fopen(fname1, "r");
if (fp1 == NULL)
goto DO_ERROR;
fp2 = fopen(fname2, "r");
if (fp2 == NULL)
goto DO_ERROR;
// その他処理
ret = TRUE;
DO_ERROR:
// デストラクト
if (fp2 != NULL)
fclose(fp2);
if (fp1 != NULL)
fclose(fp1);
return ret;
}
-----
goto は、諸悪の根源だとよく言われる。
java では廃止されてしまったし、perlでも、昔から新バージョンでは実装されない可能性が囁かれている。
しかし、正しく使えば、構造化プログラミングどころか、オブジェクト指向に近いプログラミングが可能となる。
上記のコードがそうである。
このコードは、関数レベルでオブジェクト指向に近いコードを実現している。
■「コンストラクト」のブロック
関数内にあるオブジェクトを初期化している。
■「本処理」のブロック
実際の動作を行い、何か問題が発生すれば、処理を中断して「デストラクト」に飛ぶ。
■「デストラクト」のブロック
必要な終了処理をすべて行い、関数を抜ける。
gotoを使う事によって、関数レベルで「コンストラクト」「本処理」「デストラクト」という、オブジェクト指向の基本的な考え方が実現できている。
また、原則として、処理の流れは一直線に下に延びるので、コードの可読性が非常に高い。
更に、このコードは、メンテナンス性も高い。
たとえば、第3のファイル「fname3」が必要になったと仮定しよう。
プログラムの修正はこんな手順になる。
・引数および変数「fp3」を追加。
・「コンストラクト」ブロックに変数「fp3」の初期化を記述。
・「本処理」ブロックに変数「fp3」の処理を追加。
・「デストラクト」ブロックに変数「fp3」の終了処理を追加。
ネストをいじる必要もなければ、終了処理をいろいろなところで追記する必要もない。
もちろん、上記のコードがあらゆるパターンの開発に当てはまるとは思わないが、gotoも使い方によっては非常に有用である事を、言語仕様を策定する識者の方に知っていただきたいと思う。
このエピソードはいかがでしたか? ![]() ![]() | ![]() |
投稿者 zunbe : 2005/07/16 04:59:26 | コメント (0)
2005/07/15
|
C言語には「配列名」というものがある。
これが、いわゆる「変数名」と違うという事を知っているプログラムは意外に少ない。
「配列名」は変数ではない。実行時にローカルに定まる定数である。
変数ではないのだから、値を代入する事はできない。
たとえば、以下の様な事はできない。
-----
char a[2];
a = NULL;
-----
実際、配列名 a には、メモリは割り当てられていない。
スタックの積まれ方を見ればわかる。
コードの記述の通り、メモリは2バイトしか確保されない。
配列名 a そのものに対する記憶領域は存在しないのである。
残念な事に、最近のCコンパイラでは、以下の様なコードを書いても、警告もエラーも出ない。
-----
char a[256];
sprintf("%s", &a);
-----
しかしながら、このコードは明らかに間違いである。
配列名 a には記憶領域がないのであるから、そのアドレスを求める事はできないはずだ。
正しくは、以下の様にコードを記述するべきである。
-----
char a[256];
sprintf("%s", &a[0]);
sprintf("%s", a);
-----
「&a」というコードについては、まぁ、意味はわかるので、そう目くじらを立てる事でもないとは思うが、問題は、本来の仕組みを知らずにプログラマを組んでいるプログラマが多いことである。
最近は、Visual Basicなどの便利な言語があるので、こういった細かい話はどうでもよくなりつつあるが、若いプログラマは、アセンブラやC言語をきちんと勉強して、こういった基本的な動作を知ってほしいと思う。
このエピソードはいかがでしたか? ![]() ![]() | ![]() |
投稿者 zunbe : 2005/07/15 16:33:46 | コメント (0)
|
演算には優先順位がある。
たとえば、「2-3*4」という式で、「3*4」の演算が先に行われ、次に「2-12」が計算される。
これが演算の優先順位である。
ところが、長年プログラミングをやっている人でも、評価順序があることを知らない人は多い。
評価順序とは、「3*4」という演算で、「3」が先に評価されるか、「4」が先に評価されるかという事である。
一般的に、評価は左から右に行われるので、問題になる事はまずない。
しかし、ほとんどの言語において、評価順序は処理系依存となっており、必ずしも左から右に評価されるとは限らない。
以下のコードは、よく見かけるコードだが、潜在的にバグを含んでいる。
-----
if ((p != NULL) && (p->func() == true))
{
// 処理
}
-----
このコードは、左から右に評価される事を前提に記述されているが、先の通り、評価順序は左から右とは限らない。
つまり、「p->func() == true」が先に評価される可能性があり、変数 p が NULL の時、アプリケーションエラーを引き起こす。
「式は左から右に評価される」と明示的に仕様として確定されてもよさそうな気がするのだが。
■2006/0915追記
ななしさんより、「C/C++では、&&、|| などの演算での評価順序は、左から右と規格で決まっている」というご指摘を頂きました。
調査したところ、C/C++ではご指摘の通りであり、C/C++で記述した上記のサンプルは不適切でした。
しかしながら、他の演算子や、他の言語では、やはり、評価順序は不定であるため、プログラムの作成に際して注意を要する事は変わりありません。
このエピソードはいかがでしたか? ![]() ![]() | ![]() |
投稿者 zunbe : 2005/07/15 16:18:33 | コメント (0)
| [<<先頭] [<前へ] [ 1 2 ] | ||