C言語の終端を表すEOF, NULL, ナル文字について

403, 2022-02-03

目次

C言語の終端を表すオブジェクト

C言語には「終端」を表すオブジェクトがいくつかあります。
代表的なものは

  • EOF

  • NULL

  • ナル文字

などです。
これらの終端オブジェクトについて理解しておくとC言語のプログラミングがはかどります
逆にこれらを知らないとC言語で高度なプログラミングは難しくなります。

この記事ではこれらの終端オブジェクトについて解説します。

関連記事

ファイルの終端を表す「EOF」

EOF」とは「End Of File(ファイルの終端)」の略です。
このキーワードでC言語では定数が設けられています。
この定数は標準ライブラリの「stdio.h」に定義されています。

#define EOF (-1)

この定義を見るとEOFは「-1」になっています。
しかしこれは処理系によって実装が異なる場合があります。
「EOFは-1」と決めつけてしまうと思わぬバグを生むかもしれません。

「stdio.h」ではファイル入出力にかかわる関数などが定義されています。
たとえば「fgetc()」です。
この関数は引数のファイルオブジェクトから1文字読み込む関数です。
fgetc()は読み込む際にファイルオブジェクトが終端に達していた場合、EOFを返します。

// 標準入力(stdin)から1文字読み込む
int c = fgetc(stdin);
if (c == EOF) {
    // ファイル終端に達した
}

fgetc()は読み込んだ文字をint型で返します。
↑の例ではint型の変数cfgetc()の返り値を受け取り、cがEOFでないかチェックしています。

fgetc()などの関数はファイルオブジェクトがファイル終端に達していた場合、それ以上読み込まなくなります。
一度ファイル終端に達すると返ってくるのは常にEOFです。

ちなみにEOFに達してもfseek()などでファイル位置を変更すると再び読み込めるようになります。
このような技術を「ランダム・アクセス・ファイル」と呼びます。

↓はfgetc()を使ってファイル終端まで1文字ずつ読み込み、1文字ずつ出力するコードです。

#include <stdio.h>

int main(void) {
    for (int c; (c = fgetc(stdin)) != EOF; ) {
        fputc(c, stdout);
    }

    return 0;
}

↑のようにコードを書くと標準入力への入力を標準出力に出力するようになります。

EOFが-1である理由ですが、文字コードは0以上の値で表されることがほとんどのため、空いている負数を使ったと思われます。
これの注意点は、たとえばunsigned charなどでfgetc()の返り値を受けると、EOFを検出できないなどのトラブルが起こります。

unsigned char c = fgetc(stdin);
// c は-1を表現できない

このようなコードを書くとファイルの読み込みが永遠に終わらないプログラムを作ることになります。

(^ _ ^)

無限ループってこわいね

(・ v ・)

そうだね

ポインタ配列の終端を表す「NULL」

NULL」は「ナルポインタ」と呼ばれるC言語のキーワードです。
これも定数で「stdio.h」や「stdlib.h」などで定義されています。

#define NULL ((void *) 0)

↑のように一般的な処理系ではNULLはvoid型のポインタで定義されています。
値は0であることが多いです。
しかしこれもEOFなどと同様に処理系によって定義方法が異なる場合があります。
NULLポインタを0以外で定義するのはかなりアレですが、一応決まりはありません。

NULLポインタは「何も持っていないポインタ」を表す時に使われます。
そのほか、ポインタ配列の終端を表す「番兵」としても使われることがあります。

const char *pary[] = {
    "Hello",
    "World",
    NULL,  // <- ポインタ配列paryの終端を表す番兵
};

ポインタ配列などで番兵にNULLを使うと、ポインタ配列をfor文で回すのが楽になります。
たとえば↓のコードをご覧ください。

#include <stdio.h>

int main(void) {
    const char *pary[] = {
        "My", "Name", "Is", "Anonymous", NULL,
    };

    for (const char **p = pary; *p; p += 1) {
        printf("%s\n", *p);
    }

    return 0;
}

上記のコードを実行すると↓のような結果になります。

My
Name
Is
Anonymous

↑のようにNULLポインタでポインタ配列に番兵を設けると、カウント変数などを使わずに配列を参照することができます
このような参照方法はC言語ではよく使われます。

C言語の配列から配列の要素数を計算する場合はsizeof演算子を使いますが、これは関数の引数の配列などには使えません
そのため配列の要素数をカウントしておいて変数で渡すか、あるいは構造体で配列をラップするかなどになります。
しかしポインタ配列においてはNULLポインタを使えばこのような手間は必要なくなります

ポインタ配列の場合は配列の番兵にNULLを使えばいいですが、普通の整数の配列などは困ったものです。
-1などの値を番兵に使えないこともないですが、そうすると整数として-1が使えなくなってしまいます。
このような場合は配列をラップするか要素数を計算して変数にしておくかになります。

文字列の「ナル文字」

C言語の文字列には文字配列と文字列定数がありますが、どちらの文字列もその終端を表すのは「ナル文字」と呼ばれるものです。
これはコードにすると「\0」という文字になります。
整数としての値は「0」になります。

C言語の文字列は配列と同じ構造になっています。
そのためポインタ配列などと同様に配列の終端を表すオブジェクトが必要になります。
ナル文字はその終端を表すオブジェクトです。

このナル文字がないと、C言語の文字列は文字列として機能できなくなります。
具体的には「string.h」などの関数がうまく動かなくなります。
そのためCで文字列を扱う場合は「文字列とナル文字」と言う風にセットで覚えておく必要があります。

↓はナル文字を利用して文字列をfor文で回すコードです。

#include <stdio.h>

int main(void) {
    const char *s = "Hello";

    for (; *s; s += 1) {
        printf("%c\n", *s);
    }

    return 0;
}

上記のコードを実行すると↓のような結果になります。

H
e
l
l
o

変数sには文字列"Hello"が代入されています。
このように文字列を定義すると暗黙的に文字列の末尾にナル文字が保存されます
for文でsを参照し、ナル文字(つまり0)でない間ループを回して文字を出力します。

たとえばstrlen()などの文字列用の関数です。
この関数は文字列の長さを計算する関数ですが、このときもナル文字が使われています。
文字列にナル文字が設定されていないとこのような関数は暴走してプログラムをクラッシュさせます。

おわりに

今回はC言語の「終端」を表すオブジェクト、EOF, NULL, ナル文字について解説しました。
これらのオブジェクトはC言語では非常によく使われるので覚えておいて損はありません。
覚えてしまってC言語マスターを目指しましょう。

(^ _ ^)

目指せC言語マスター

(・ v ・)

スーパーハカー



この記事のアンケートを送信する