WisdomSoft - for your serial experiences.

トークンと文

C 言語のトークンと文について解説します。コードに書かれているすべての文字並びや記号はトークンという最小単位に分解できます。複数のトークン並びは文と呼ばれる最小の実行単位となります。

コードの最小単位

どんなに複雑に書かれたプログラムも、トークン(token)と呼ばれる最小単位のテキストに分解することができます。トークンは、それ以上分解することができないプログラムの最小単位であり、英語における英単語に相当するものです。プログラミング言語の命令となる文は、複数のトークンによって構成されています。

プログラミング言語を機械語に翻訳するまでにいくつかの工程を挟みます。コンパイラに入力されたテキストは、ソースコードに書かれたテキストが C 言語で定めている文法に従っているかどうかを調べるためにトークンの列に分解されます。この工程を字句解析と呼びます。

トークンにはいくつか種類があり、プログラミング言語の仕様で定められたキーワード、操作対象の名前を表す識別子、計算記号などの演算子などがあります。トークンは、次の 6 つに分類されます。

  • 識別子(identifer)
  • キーワード(keyword)
  • 定数(constant)
  • 文字列リテラル(string-literal)
  • 演算子(operator)
  • 区切り子(punctuator)

キーワードとは C 言語が仕様レベルで予約している名前(英単語)のことで、前回のプログラムで紹介した return などはキーワードに属します。Visual C++ など、標準的な開発環境のテキストエディタでは青色で表示されます。

識別子とは、操作するデータや命令を識別するためにソース上で命名された名前です。例えば、main などの関数名は識別子です。

定数とは、コード上で指定された固定的な値でのことです。例えば 10 や 3.14 というような数値は定数です。

文字列リテラルも定数と同様にコード上の固定値で、二重引用符で囲まれた複数の文字を表します。文字列リテラルによって、コード上に任意のテキストを埋め込むことができます。

演算子は、計算のために用いる記号のことで + 記号や - 記号は演算子に属します。

区切り子とは [ ] ( ) { } , : ; などの記号のことで、何らかの要素を区切ったりまとめたりすることを表す記号として用いられています。一部の記号は演算子と同じですが、記号が出現する位置によって演算子なのか区切り子なのかを判断できます。main 関数の名前の後にある括弧や、命令の末尾を表すセミコロン ; などは区切り子に分類されます。

個々のトークンの意味は、C 言語の学習を進めることで少しずつ理解していけるでしょう。重要なのは、ソースコード上に書かれるあらゆる記号やアルファベットは、上記のトークンのいずれかに分類できるということです。

コンパイル時に文法などでエラーが出る場合は、書かれているコードが正しいトークンの並びであるかどうかを調べることで間違いを探しやすくなるでしょう。慣れた技術者であれば、意識することなくトークンの種類や並びを把握してコンパイラに頼ることなくコードが正しいかどうかを判断できます。

英文と同じように、トークンは空白によって区切られます。正確には、ホワイトスペースと呼ばれる次の文字によってトークンを区切ることができます。

  • スペース
  • 水平タブ
  • 垂直タブ
  • 改行
  • 改ページ

このうちの垂直タブと改ページは、主にプリンタを対象とした専用の文字であり、現在の一般的な PC やテキストエディタでは使われません。Visual Studio による開発において利用するホワイトスペースはスペース、水平タブ、改行のいずれかになるでしょう。

コード1
	int
main (
void
){
		return
0;
	}

上記のコード1は、コードがめちゃくちゃになっているように見えますが正常にコンパイルして実行できます。空白や改行位置がバラバラなので読みにくいですが、個々の単語や記号の並びをトークンの列として見ると正しいことが確認できます。

同様に、トークンの並びが正しければ水平タブや改行を使わずに 1 行でプログラムを書くこともできます。

コード2
int main(){return 0;}

演算子や区切り子と呼ばれる記号のトークンは、前後のトークンを区切る性質を持ち合わせています。よって丸括弧 (  ) や中括弧 { }、セミコロン ; などの前後のトークンには空白が無くても問題ありません。

一方で、トークンの区切りが適切に行われていなければコンパイラエラーとなるでしょう。例えば、次のようなコードは構文エラーとなります。

intmain(){return0;}

この場合、冒頭のキーワード int と関数名 main が区切られていないため intmain という 1 つのトークンとして解釈されてしまいます。また、関数内の return キーワードと数値 0 の間も区切られていないため return0 というトークンとして解釈されます。コンパイラは、この名前を処理できずにエラーを報告することになります。

逆に区切ってはならない部分にホワイトスペースが挿入された場合でも、トークンが余分に分離されてしまいエラーとなってしまうでしょう。

int mai n(){ retu rn 0; }

上記のコードは、関数名となる識別子 main の途中がスペースによって区切られてしまい mai た n という 2 つのトークンに分けられています。コンパイラは、識別子 mai の直後に現れた n が構文不正であると判断してエラーを報告します。同様に return キーワードもスペースによって retu と rn という 2 つのトークンに分解されてしまっています。コンパイラは retu や rn という名前を解決できないため、やはりエラーとなります。

命令の実行単位

複数のトークンからなる 1 つの実行単位を(statement)と呼びます。つまり、コンピュータに対する命令は文単位であり、関数は文の集合であると考えることができます。多くの文はセミコロン ; で終了するため、コンパイラはセミコロンを見つけることによって文が終了したと認識できます。例えば次のプログラムは 2 つのステートメントで成り立っています。

printf("Stand by Ready!!\n");
return 0;

画面にテキストを表示するための printf() 関数の行と return キーワードから始まる行の末尾にセミコロン ; 記号があります。これらは、ここで文が終了していることを表しています。コードを見やすくするために文が終わると改行を入れますが、改行自体は意味を持たないので、望むのであれば 1 行で複数の文を記述できます。

コード3
#include <stdio.h>
int main() { printf("Stand by Ready!!\n"); return 0; }
実行結果
コード3 実行結果

コード3の main() 関数内では複数の文を改行せずに記述していますが、コンパイラはセミコロンで文の終端を認識できるため問題なくコンパイルができます。ただし、最初の行の #include プリプロセッサディレクティブは文ではないので改行で終了させる必要があります。

文といってもいくつかの種類が存在し、大きく次のように分類されています。

  • 名札付き文(labeled-statement)
  • 式文(expression-statement)
  • 複合文(compound-statement)
  • 選択文(selection-statement)
  • 繰り返し文(iteration-statement)
  • ジャンプ文(jump-statement)

このうち、一般的な計算や機能の呼び出しは式文に分類されます。関数の中に記述される文の多くが式文となるでしょう。式文やジャンプ文などでは末尾にセミコロン ; をつけなければならない決まりになっているのですが、必ずしもすべての文にセミコロンが付けられるわけではありません。

例えば、複合文にはセミコロンをつけません。複合文とは関数本体(function-body)に用いている { } のことで、ブロックとも呼ばれています。関数本体は 1 つの複合文と考えることができますが、関数の末尾に ; をつける必要がないのは、複合文の末尾は開始を表す中括弧 { に対応する } で判断されるためです。

それぞれの文が具体的にどのようなものかは、これから順番に説明していきます。今は、文が上記のように分類されていることを覚えておいてください。