今日のコード (1)
このシリーズについて
書いて、自分でそれなりによく書けたなあというコードが、たまにあります。 きれいにデザインパターンを取り入れられてるとか、メモリ管理が良く出来てるとか、速度が速いとか、それだけでなく、美しいなあ、と思えるコードがあるのです。
そういうコードがあった時に紹介するという試みをやってみようと思います。毎日書くわけではありませんが、「今日のコード」としてみました。
プログラムというのはただのツールだとも言い切れない、と僕は思うのです。
言語
今回の言語は、Cです。
Cはメモリの使い方から関数や構造体といったものの持つ意味合いまで自分なりに決められる、どんな性格の人でも受け入れやすい言語だと感じます。
最近は最も有名なCコンパイラであるGCCやMac OS Xの標準となったclangも中身はC++であったりするので、だんだん製品からは消えてきていますが、アマチュアプログラマーにとってこの言語以上にプログラムの可能性を感じさせてくれるような言語は未だ存在しないのではないでしょうか。
ソースコード
#include <stdlib.h> #define CODE_LENGTH 64 typedef struct Code_tag Code; struct Code_tag{ char code[CODE_LENGTH]; Code *next; }; typedef struct Source_tag Source; struct Source_tag{ Code *codes; int codes_len; Source *next; }; Code *create_code(){ Code *code = (Code *)malloc(sizeof(Code)); code->next = NULL; return code; } Source *create_source(){ Source *source = (Source *)malloc(sizeof(Source)); source->codes = create_code(); source->codes_len = 0; source->next = NULL; return source; } Source *get_strings(FILE *in){ Source *source = create_source(); Source *cur_source = source; Code *code = cur_source->codes; int i = 0; char c; for(c = fgetc(in); c != EOF; c = fgetc(in)){ if(c == '\n'){ code->code[i] = '\0'; cur_source->next = create_source(); cur_source = cur_source->next; code = cur_source->codes; i = 0; } else{ if(i == CODE_LENGTH){ code->next = create_code(); code = code->next; i = 0; } code->code[i++] = c; cur_source->codes_len++; } } return source; }
解釈
なかなかのボリュームがありますが、これはGPGPU上でBrainfuckインタプリタを実装するために、ファイルから改行で区切られた複数のBrainfuckソースコードをメモリ上に読み込む関数get_strings
を定義したものです。
入力ファイルの例としては、以下の様なものが挙げられます。
>++++++++[<+++++++++>-]<. >++++++++++[<++++++++++>-]<+. >++++++++++[<+++++++++++>-]<--. >++++++++++[<+++++++++++>-]<--. >++++++++++[<+++++++++++>-]<+. >++++[<+++++++++++>-]<. >++++[<++++++++>-]<. >+++++++++[<++++++++++>-]<---. >++++++++++[<+++++++++++>-]<+. >++++++++++[<+++++++++++>-]<++++. >++++++++++[<+++++++++++>-]<--. >++++++++++[<++++++++++>-]<. >++++[<++++++++>-]<+. .
上記は上から順に
H
、e
、l
、l
、o
、,
、、
W
、o
、r
、l
、d
、!
、\0
を出力するBrainfuckプログラムです。
最初は普通にfgets
で読み込み、1つづつのchar
型の配列にしてその配列の配列を返す関数にしようかと考えていましたが、後々文字数が必要になることを考えると、strlen
などを使って再び1文字づつのループを回すのは気が引けたので、はじめからfgetc
を用いることにしました。
なお、fgetc
とgetc
の違いについては何気にWikipediaの記事が分かりやすいです。
Source
構造体は1行に書かれているソースコードを表し、Code
構造体はリストとなってそのソースコードのすべての文字列を保持します。Source
構造体をリストとしたものの先頭ポインタを返すことで、get_strings
の戻り値が極めてわかりやすくなっていることが伺えます。
また、関数内の各行も、一般的な型名から構造体の名前になっていることで、英語のようにスラスラと読み下せるようになります。ここまで簡潔であれば、コメントを書く必要など無いでしょう。
また、プログラマから見た見やすさだけでなく、Code
構造体によってどのような長さのソースコードが1行に書かれていても物理的にメモリが許せばその全てをSource
構造体に格納し切ることが出来ますし、realloc
などという挙動の怪しい関数を用いる必要もなくなります。もし、実行速度の効率よりも可能な限り無駄なメモリを使用したくないと考えるのであれば、CODE_LENGTH
の値をもっと小さくすればいいでしょう。或いは、この値を実行時のコマンドライン引数でユーザに与えさせるというのも悪くないアイディアです。
もしかすると、for文の書き方については異論のある人が多いかもしれませんが、普段イテレータを初期化したり更新したりという操作をforの括弧内で行っていることを鑑みれば、こうした書き方もそれほど忌むべきもので無いのではないかと思い、あえてfgetc(in)
を2度書く形をとりました。
例えばif(i == CODE_LENGTH)
のようなところで>=
なんかを使わないことによる機械に対する信頼やcode[i++]
とするかfor文の最後に..., i++){
とするかという葛藤の話もしたいのですが、ここはグッと堪えて、細部については実際のコードを読んで感じ取ってもらうようにしたいと思います。
最後に
かなりスペシフィックなコードですので、誰のコーディングにも役には立たないかと思いますが、プログラムをただのツールでなく、ひとつの作品として愛でるような場がなかなかなかったので、自分だけでこっそりと始めてみてしまいました。
もし、運悪くこの記事を読んでしまった方がいらっしゃいましたら、ごめんなさい。ただもし少しでもご興味が有るのなら、ぜひご自身のプログラミング言語観などをコメントしていただければと思います。