4回目となる今回はC言語のソースコードを読んでいく上で欠かせない構造体を紹介する。マクロと同じく、これも知っておかないとC言語のソースコードは読めないと思う。
学校の授業や演習で習うC言語の勉強はその後の仕事に結びつかないことが多い。この原因の1つに、C言語に出てくる型やマクロ定義など、授業で習う以外のものが実際のC言語のコードにはたくさん使われているからではないかと思う。例えば、次のソースコードを見てほしい。これはkilo.cの850行目辺りのコードだが、授業でC言語に触れた程度だと、まずこの struct abufという構造体に面食らうはずだ。「なんだこれは、習っていないぞ」と。
void abFree(struct abuf *ab) {
free(ab->b);
}
それもそのはずで、この構造体はKilo用に定義された構造体で、kilo.cの830行目辺りで次のように定義されている。出力する時に利用することを想定したバッファとして用意されたものであることがわかる。
/* We define a very simple "append buffer" structure, that is an heap
* allocated string where we can append to. This is useful in order to
* write all the escape sequences in a buffer and flush them to the standard
* output in a single call, to avoid flickering effects. */
struct abuf {
char *b;
int len;
};
ほかの関数も見て見よう。kilo.cの855行目辺りから始まっているeditorRefreshScreen()関数の冒頭部分を見てみると、次のようなコードを確認できる。
/* This function writes the whole screen using VT100 escape characters
* starting from the logical state of the editor in the global state 'E'. */
void editorRefreshScreen(void) {
int y;
erow *r;
char buf[32];
struct abuf ab = ABUF_INIT;
abAppend(&ab,"\x1b[?25l",6); /* Hide cursor. */
abAppend(&ab,"\x1b[H",3); /* Go home. */
...略...
}
構造体abufとしてabが定義されているわけだが、この初期値としてABUF_INITが使われている。これも学校で習うものではなく、kilo.cの840行目辺りで次のように定義されたマクロであることを確認できる。
#define ABUF_INIT {NULL,0}
このようにマクロと構造体はいろいろな場面で使われている。そして、マクロも構造体もある概念や意味を整理して実装したものになっていることが多い。この部分を読むとC言語の実装上の仕組みから、大体このような実装にしてあるのだろうといったことが推測でき、かつ、どのような考えでソースコードが記述されたのかもくみ取ることができる。
kilo.cで重要な構造体はあと2つ(または3つ)だ。まず、1行分のデータを表現する構造体erow。
/* This structure represents a single line of the file we are editing. */
typedef struct erow {
int idx; /* Row index in the file, zero-based. */
int size; /* Size of the row, excluding the null term. */
int rsize; /* Size of the rendered row. */
char chars; / Row content. */
char render; / Row content "rendered" for screen (for TABs). */
unsigned char hl; / Syntax highlight type for each character in render.*/
int hl_oc; /* Row had open comment at end in last syntax highlight
check. */
} erow;
次は構造体editorConfigだ。editorConfigから先ほど取り上げた構造体erowへのポインタが保持されている。editorConfigには行数などが確保されており、editorConfigが対象のファイル全体を表現する構造体になっている。ここからそれぞれの行のデータがerowで表現されるデータにポインタでつながっている構造になっていることがわかる。
struct editorConfig {
int cx,cy; /* Cursor x and y position in characters */
int rowoff; /* Offset of row displayed. */
int coloff; /* Offset of column displayed. */
int screenrows; /* Number of rows that we can show */
int screencols; /* Number of cols that we can show */
int numrows; /* Number of rows */
int rawmode; /* Is terminal raw mode enabled? */
erow *row; /* Rows */
int dirty; /* File modified but not saved. */
char *filename; /* Currently open filename */
char statusmsg[80];
time_t statusmsg_time;
struct editorSyntax *syntax; /* Current syntax highlight, or NULL. */
};
editorConfigにはeditorSyntaxという構造体も含まれている。これもkilo.cで定義された独自の構造体で、次のような定義になっている。構造としては複数のプログラミング言語のシンタックスに対応できる仕組みにしてあるが、とりあえず実装としてはC/C++の分だけ追加してあるということがわかる。短いコードの中によくまとまっている。
editorConfig構造体の中にあるeditorSyntax構造体の定義部分↓
struct editorSyntax {
char **filematch;
char **keywords;
char singlelinecommentstart[2];
char multilinecommentstart[3];
char multilinecommentend[3];
int flags;
};
editorSyntax構造体の変数の初期化部分↓
char *CHLextensions[] = {".c",".cpp",NULL};
char *CHLkeywords[] = {
/* A few C / C++ keywords */
"switch","if","while","for","break","continue","return","else",
"struct","union","typedef","static","enum","class",
/* C types */
"int|","long|","double|","float|","char|","unsigned|","signed|",
"void|",NULL
};
struct editorSyntax HLDB[] = {
{
/* C / C++ */
CHLextensions,
CHLkeywords,
"//","/","/",
HLHIGHLIGHTSTRINGS | HLHIGHLIGHTNUMBERS
}
};
ここが特に重要なのだが、C言語がある程度わかる方なら、ここで取り上げたように定義されている構造体を見ることで、開発者がどのような考えでエディタのデータをメモリに保持しているのかを理解することができる。
この構造体はエディタ上のデータを保持する作りになっている。さらに丁寧にコメントに考え方と使い方、なぜこうした構造体にしたのかまで書いてある。わかるようになってくると、ここを見るとこのエディタがどのような考え方で実装され、どのような用途を想定しているものか、どの程度の利便性を考慮したものかなどを推測できる。
実際のところ、Kiloのメモリ構造はエディタとしてはシンプルなほうに分類されるのだが、プログラミングをしない方にとってはそろそろブラウザのタブを閉じるころだと思う。だが待ってほしい! もうちょっと待ってほしい!
確かに、ちょっとばかりメモリの構造が複雑に見えるかもしれない。実際、エディタは使いたいけれど、作りたくはないアプリケーションでもある(効率のよいエディタを作ろうとするといろいろやらないといけないので面倒)。
しかし、だ。全部理解しなくてもいい。ソースコードを読む時は当たりを付けて読んでいき、わからないところはわからないままにしておけばよい。今回の説明も、よくわからないなら、「よくわからないが〇〇という構造体がエディタのデータをメモリに保持しているみたいだ」ということが把握できればよい。
Kiloで面白いのはライブラリを使わずに直接エスケープシーケンスを使っている点だ。ここまで必要最小限と思われる部分を説明してきたので、次回からはエスケープシーケンスを使って、どのようなことをやっているのかについて説明していこうと思う。