ヘッダファイル

昨日は、btreesort を作るのに main.c と btree.c の 2つのファイルに分割した。main.c では btree.c の中で定義している BTreeSort() 関数を使うために、関数プロトタイプを記述した。
さて、例えばプログラムがもっと大規模になって、ソースファイルの数も増えるとしよう。BTreeSort() 関数もいくつかのソースファイルから使うようになるかもしれない。そうした場合、そのいくつかのソースファイル全てに関数プロトタイプを記述するのは効率が悪い。また、間違いのもとでもある。今回の例では関数ひとつしかないけど、一般的に考えればもっと多くの関数があるかもしれないし、構造体などの定義も必要かもしれない。そうなると、関数や構造体を使用するソースファイル全てに間違いなく記述するのは困難になってくる。
それを解決するのがヘッダファイル(*.h ファイル)だ。外部に公開したい関数のプロトタイプなどはヘッダファイルに記述しておき、それを使いたいソースファイルではそのヘッダファイルをインクルードすればいい。

早速やってみよう。今回の例では、btree.h というファイル名になる。

void BTreeSort(int ary[], const int n);

公開したい関数がひとつしかないので、ヘッダファイル(btree.h)もその関数プロトタイプだけだ。
一般的には、次のようなものをヘッダファイルに記述する。

  • 外部に公開するマクロの定義(関数型マクロの定義)
  • 外部に公開する定数の定義(#define や enum による定義)
  • 外部に公開する構造体、共用体の定義
  • 外部に公開する型の定義(typedef による型の定義)
  • グローバル関数のプロトタイプ宣言
  • グローバル変数の extern 宣言

5つ目のが今回の場合に当たる。

で、ソースファイル(main.c、btree.c)の方では、次のように btree.h をインクルードする。

#include
#include
#include

#include "btree.h"

#define MAX 10

void PrintArray(int ary[]);

int main(void)
{
    int i;
    int ary[MAX];

    srand((unsigned)time(NULL));

    for (i = 0; i < MAX; i++) {
        ary[i] = rand() % 100;
    }
    printf("unsorted: ");
    PrintArray(ary);
    BTreeSort(ary, MAX);
    printf("sorted: ");
    PrintArray(ary);

    return 0;
}

void PrintArray(int ary[])
{
    int i;

    for (i = 0; i < MAX; i++) {
        printf("%2d ", ary[i]);
    }
    printf("\n");
}
#include

#include "btree.h"

typedef struct tree {
    int value;
    struct tree *left;
    struct tree *right;
} Tree;

Tree *Insert(Tree *root, int val);
int Traverse(Tree *root, int ary[], int n);
void FreeTree(Tree *root);

void BTreeSort(int ary[], const int n)
{
    int i;
    Tree *root = NULL;

    for (i = 0; i < n; i++) {
        root = Insert(root, ary[i]);
    }
    Traverse(root, ary, 0); FreeTree(root);
}

Tree *Insert(Tree *root, int val)
{
    if (root == NULL) {
        root = (Tree *)malloc(sizeof(Tree));
        root->value = val;
        root->left = NULL;
        root->right = NULL;
    } else {
        if (val < root->value) {
            root->left = Insert(root->left, val);
        } else {
            root->right = Insert(root->right, val);
        }
    }

    return root;
}

int Traverse(Tree *root, int ary[], int n)
{
    if (root->left != NULL) {
        n = Traverse(root->left, ary, n);
    }
    ary[n++] = root->value;
    if (root->right != NULL) {
        n = Traverse(root->right, ary, n);
    }

    return n;
}

void FreeTree(Tree *root)
{
    if (root == NULL) {
        return;
    } else {
        FreeTree(root->left);
        FreeTree(root->right);
        free(root);

        return;
    }
}

うまくコンパイルできるかな?

takatoh@nightschool $ ls
btree.c  btree.h  main.c
takatoh@nightschool $ gcc -Wall main.c -c
takatoh@nightschool $ gcc -Wall btree.c -c
takatoh@nightschool $ ls
btree.c  btree.h  btree.o  main.c  main.o
takatoh@nightschool $ gcc main.o btree.o -o btreesort
takatoh@nightschool $ ls
btree.c  btree.h  btree.o  btreesort  main.c  main.o
takatoh@nightschool $ ./btreesort
unsorted: 89 46 78 22  0 23 82 86 84 64 
sorted:    0 22 23 46 64 78 82 84 86 89

出来た。

分割コンパイル

プログラムの規模が大きくなると、ソースファイルを複数に分割して記述するのが普通だ。
というわけで、試しに昨日の btreesort を分割してみた。ソートを実装する btree.c と main() 関数のある main.c だ。
まずは main.c。

#include
#include
#include

#define MAX 10

void PrintArray(int ary[]);
void BTreeSort(int ary[], const int n);

int main(void)
{
    int i;
    int ary[MAX];

    srand((unsigned)time(NULL));

    for (i = 0; i < MAX; i++) {
        ary[i] = rand() % 100;
    }
    printf("unsorted: ");
    PrintArray(ary);
    BTreeSort(ary, MAX);
    printf("sorted: ");
    PrintArray(ary);

    return 0;
}

void PrintArray(int ary[])
{
    int i;

    for (i = 0; i < MAX; i++) {
        printf("%2d ", ary[i]);
     }
    printf("\n");
}

10行目に注目。BTreeSort() の関数プロトタイプを記述して、このファイルの中で使えるようにしてある。 で、その BTreeSort() 本体は btree.c に記述する。

#include

typedef struct tree {
    int value;
    struct tree *left;
    struct tree *right;
} Tree;

void BTreeSort(int ary[], const int n);
Tree *Insert(Tree *root, int val);
int Traverse(Tree *root, int ary[], int n);
void FreeTree(Tree *root);

void BTreeSort(int ary[], const int n)
{
    int i;
    Tree *root = NULL;

    for (i = 0; i < n; i++) {
        root = Insert(root, ary[i]);
    }
    Traverse(root, ary, 0);
    FreeTree(root);
}

Tree * Insert(Tree *root, int val)
{
    if (root == NULL) {
        root = (Tree *)malloc(sizeof(Tree));
        root->value = val;
        root->left = NULL;
        root->right = NULL;
    } else {
        if (val < root->value) {
            root->left = Insert(root->left, val);
        } else {
            root->right = Insert(root->right, val);
        }
    }

    return root;
}

int Traverse(Tree *root, int ary[], int n)
{
    if (root->left != NULL) {
        n = Traverse(root->left, ary, n);
    }
    ary[n++] = root->value;
    if (root->right != NULL) {
        n = Traverse(root->right, ary, n);
    }

    return n;
}

void FreeTree(Tree *root)
{
    if (root == NULL) {
        return;
    } else {
        FreeTree(root->left);
        FreeTree(root->right);
        free(root);
        return;
    }
}

さて、分割コンパイルだ。
今まではソースファイルが1つだったので、ひとつの gcc コマンドでいっぺんに実行ファイルを作っていた。実際には、gcc は *.c ファイルから *.o ファイルを作り、*.o ファイルから実行ファイルを作る。*.o ファイルをオブジェクトファイルとよび、オブジェクトファイルを作ることをコンパイル、オブジェクトファイルから実行ファイルを作ることをリンクという。リンクは複数のオブジェクトファイルのほか、標準ライブラリなどもリンクする。
gcc では -c オプションをつけることで、上に書いたコンパイル、つまり実行ファイルじゃなくて *.o ファイルを作ることができる。複数のソースファイルに分けた場合、ひとつずつコンパイルすること(オブジェクトファイルを作ること)を分割コンパイルという。
オブジェクトファイルは実行ファイルではないので、最終的に実行ファイルを作るには、リンクする必要がある。じゃあなぜ分割コンパイルするのかといえば、例えばあるソースファイルだけ修正した場合、それだけコンパイルしなおせば、あとはコンパイル済みのオブジェクトファイルとリンクすればいい。つまりコンパイルの時間(と手間)が省力化できるってわけだ。

やってみよう。まずは main.c から。

takatoh@nightschool $ ls
btree.c  main.c
takatoh@nightschool $ gcc -Wall main.c -c
takatoh@nightschool $ ls
btree.c  main.c  main.o

オブジェクトファイル main.o が出来た(警告も出ていない)。
つぎ、btree.c。

takatoh@nightschool $ gcc -Wall btree.c -c
takatoh@nightschool $ ls
btree.c  btree.o  main.c  main.o

こちらもオブジェクトファイルが出来た。
これら2つのオブジェクトファイルをリンクするには次のようにする。

takatoh@nightschool $ gcc main.o btree.o -o btreesort
takatoh@nightschool $ ls -l
合計 28
-rw-rw-r-- 1 takatoh takatoh 1384  6月 27 08:01 btree.c
-rw-rw-r-- 1 takatoh takatoh 2360  6月 27 13:48 btree.o
-rwxrwxr-x 1 takatoh takatoh 9056  6月 27 13:49 btreesort
-rw-rw-r-- 1 takatoh takatoh  614  6月 27 08:00 main.c
-rw-rw-r-- 1 takatoh takatoh 2248  6月 27 13:47 main.o

これで実行ファイル btreesort が出来た。
うまく動くか試してみよう。

takatoh@nightschool $ ./btreesort
unsorted: 59 12 88 98 83 78  6 38 88 55 
sorted:    6 12 38 55 59 78 83 88 88 98

OK。大丈夫みたい。

gcc -Wallオプション

入門書「独習 C」を読み終わったので、今度は「C言語 入門書の次に読む本」というのを読み始めた。
で、gcc に -Wall オプションがあるのを知った。-Wall オプションは、コンパイル時にいろいろな警告を出すオプションのあらかた(全部じゃない)をまとめて指定するオプションで、これをつけてコンパイルすることでかなり細かい警告を出してくれる。この本では -Wall オプションをつけてコンパイルすることを強く薦めている。

じゃ、早速やってみよう。ネタは先日の btreesort.c。

takatoh@nightschool $ gcc -Wall btreesort.c -o btreesort
btreesort.c: In function ‘main’:
btreesort.c:28:5: warning: implicit declaration of function ‘time’ [-Wimplicit-function-declaration]
     srand((unsigned)time(NULL));
     ^
btreesort.c:26:11: warning: unused variable ‘root’ [-Wunused-variable]
     Tree *root = NULL;
           ^

コンパイルは出来たけど、警告が2つ出た。1つ目の implicit declaration of function ‘time’ は time 関数の暗黙の宣言という警告で、time.h をインクルードしていないのが原因のようだ。gcc は宣言のない関数を見つけると、その返り値が int だと仮定してコンパイルするらしい。とにかく、これは time.h をインクルードして解決。
2つ目の unused variable ‘root’ は使ってない変数があるってことだろう。これはこの変数の宣言を消せばOK。
次のように修正した。

takatoh@nightschool $ git diff
diff --git a/sandbox/btreesort.c b/sandbox/btreesort.c
index a96a16d..08da355 100644
--- a/sandbox/btreesort.c
+++ b/sandbox/btreesort.c
@@ -1,5 +1,6 @@
 #include <stdio.h>
 #include <stdlib.h>
+#include <time.h>
 
 
 #define MAX 10
@@ -23,7 +24,6 @@ int main(void)
 {
     int i;
     int ary[MAX];
-    Tree *root = NULL;
 
     srand((unsigned)time(NULL));
 

もう一度コンパイルしてみよう。

takatoh@nightschool $ gcc -Wall btreesort.c -o btreesort
takatoh@nightschool $ ./btreesort
unsorted: 55 57 58 63 86 24 12 74 84 29 
sorted:   12 24 29 55 57 58 63 74 84 86

今度は何も警告なしにコンパイルできた。

[amazonjs asin=”4774146129″ locale=”JP” title=”C言語 入門書の次に読む本 改訂新版 (プログラミングの教科書)”]

二分木を使ったソート

単純な二分木を使ったソート。
構造体で二分木を表現するのと、malloc() / free() の練習。

#include
#include

#define MAX 10

typedef struct tree {
    int value;
    struct tree *left;
    struct tree *right;
} Tree;

void PrintArray(int ary[]);
void BTreeSort(int ary[], const int n);
Tree *Insert(Tree *root, int val);
int Traverse(Tree *root, int ary[], int n);
void FreeTree(Tree *root);

int main(void)
{
    int i;
    int ary[MAX];
    Tree *root = NULL;

    srand((unsigned)time(NULL));

    for (i = 0; i < MAX; i++) {
        ary[i] = rand() % 100;
    }
    printf("unsorted: ");
    PrintArray(ary);
    BTreeSort(ary, MAX);
    printf("sorted: ");
    PrintArray(ary);

    return 0;
}

void PrintArray(int ary[])
{
    int i;

    for (i = 0; i < MAX; i++) {
        printf("%2d ", ary[i]);
    }
    printf("\n");
}

void BTreeSort(int ary[], const int n)
{
    int i;

    Tree *root = NULL;
    for (i = 0; i < n; i++) {
        root = Insert(root, ary[i]);
    }
    Traverse(root, ary, 0);
    FreeTree(root);
}

Tree *Insert(Tree *root, int val)
{
    if (root == NULL) {
        root = (Tree *)malloc(sizeof(Tree));
        root->value = val;
        root->left = NULL;
        root->right = NULL;
    } else {
        if (val < root->value) {
            root->left = Insert(root->left, val);
        } else {
            root->right = Insert(root->right, val);
        }
    }

    return root;
}

int Traverse(Tree *root, int ary[], int n)
{
    if (root->left != NULL) {
        n = Traverse(root->left, ary, n);
    }
    ary[n++] = root->value;
    if (root->right != NULL) {
        n = Traverse(root->right, ary, n);
    }

    return n;
}

void FreeTree(Tree *root)
{
    if (root == NULL) {
        return;
    } else {
        FreeTree(root->left);
        FreeTree(root->right);
        free(root);
        return;
    }
}
takatoh@nightschool $ ./btreesort
unsorted: 80 77 21 42 50 76 88 48 32 19 
sorted:   19 21 32 42 48 50 76 77 80 88

C99

以前コメントでもらったけど、現在の標準の C というのは、ANSI C ではなく C99(ISO/IEC 9899:1999)のことのようだ。入門書(「独習 C」)もひと通り読み終わったことだし、ここで C99 の新機能について主なところをまとめておこう。
参考にしたのはこのページ。

 cf. C99の仕様 – C言語最新事情

bool型

真偽値を表すブール型が追加された。標準では型名が _Bool となっているけど、stdbool.h を読み込むことで bool という型名と、値として true、false が使えるようになる。

#include
#include

bool isoctal(char c)
{
    return '0' <= c && c < '8';
}

int main(void)
{
    char c = '3';
    if (isoctal(c)) {
        printf("%c is octal.\n", c);
    }
    return 0;
}
takatoh@nightschool $ ./bool
3 is octal.

可変長配列

可変長といっても自由に長さを変えられるのではなくて、実行時に配列の長さを指定できるようになった。

#include

size_t fsize3(int n)
{
    char b[n + 3];

    return sizeof b;
}

int main(void)
{
    printf("%ld\n", fsize3(10));

    return 0;
}
takatoh@nightschool $ ./array
13

C++コメント

// 以降行末までがコメントとして扱われるようになった。

関数名マクロ

__func__ というマクロが追加された。これは現在の関数の名前を表す。

#include

void myfunc(void)
{
    printf("%s\n", __func__);
}

int main(void)
{
    myfunc();

    return 0;
}
takatoh@nightschool $ ./mfunc
myfunc

指示初期化子

指示初期化子を使うと、配列の特定の添字、構造体の特定のメンバを初期化できる。
まずは配列の場合:

#include

void PrintArray(int ary[]);

int main(void)
{
    int a[] = {
        [9] = 1
    };

    PrintArray(a);

    return 0;
}

void PrintArray(int *ary)
{
    int i;

    for (i = 0; i < 10; i++) {
        printf("%d ", ary[i]);
    }
    printf("\n");
}
takatoh@nightschool $ ./init1
0 0 0 0 0 0 0 0 0 1

上の例では、配列の添字9の要素を 1 に初期化している。この場合配列は次のように初期化される。

  • 配列のサイズが未知の場合、指示初期化子で指定された最大のものをもとにサイズが決定される。
  • 指示初期化子で指定されなかった添字の領域は、int型では 0 に初期化され、ポインタではヌルポインタに初期化される。

指示初期化子は複数指定することも可能。また、通常の初期化子と混ぜて使うことも可能で、通常の初期化子は指示初期化子の添字に続くものとして解釈される。

#include

void PrintArray(int ary[]);

int main(void)
{
    int a[] = {
        [3] = 1,
        2,
        [8] = 3,
        4
    };

    PrintArray(a);

    return 0;
}

void PrintArray(int *ary)
{
    int i;

    for (i = 0; i < 10; i++) {
        printf("%d ", ary[i]);
    }
    printf("\n");
}
takatoh@nightschool $ ./init2
0 0 0 1 2 0 0 0 3 4

構造体の指示初期化子は「.メンバー」。

#include

typedef struct {
    int x;
    int y;
    int width;
    int height;
} Rectangle;

int main(void)
{
    Rectangle r = { .x = 1 };

    printf("x = %d\n", r.x);
    printf("y = %d\n", r.y);
    printf("widht = %d\n", r.width);
    printf("height = %d\n", r.height);

    return 0;
}
takatoh@nightschool $ ./init3
x = 1
y = 0
widht = 0
height = 0

変数宣言

ANSI C では変数の宣言はブロックの先頭で行う必要があったけど、C99 ではブロックの途中でも宣言できるようになった。また、for 文に限ってループ変数の宣言が可能になった。このループ変数は for ブロックの中だけで有効。

#include
#include

int sum(int count, int a[])
{
    assert(count >= 0);

    int sum = 0;
    // Error in ANSI-C
    for (int i = 0; i < count; i++) {
        // Error in ANSI-C
        sum += a[i];
    }
    return sum;
}

int main(void)
{
    int data[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};

    printf("sum = %d\n", sum(sizeof(data) / sizeof(data[0]), data));

    return 0;
}
takatoh@nightschool $ ./localvar
sum = 45

複合リテラル

構造体や配列(ただし可変長配列を除く)をリテラルとして生成できるようになった。

#include

typedef struct {
    int x;
    int y;
} Point;

Point Center(const Point *p1, const Point *p2)
{
    return (Point) {
        .x = (p1->x + p2->x) / 2,
        .y = (p1->y + p2->y) / 2
    };
}

int main(void)
{
    Point p = Center(&(Point){1, 1}, &(Point){3, 3});
    printf("(%d, %d)\n", p.x, p.y);

    return 0;
}
takatoh@nightschool $ ./compound_literal
(2, 2)

上の例では、Center 関数の呼び出し時と、Center 関数が値を返すときに構造体の複合リテラルを使っている。
……ところで複合ってどういう意味だろう?

さて、こんなところだろうか。

動的なメモリ割り当て

動的なメモリ割り当て(dynamic allocation)とは、プログラムの実行中に必要に応じてメモリを割り当てる処理のこと。これまで使ってきた変数は、必要なメモリがコンパイル時に固定されているのに対して、動的なメモリ割り当ては実行時にメモリを割り当てることができる。

メモリを割り当てるには、malloc() 関数を使う。

void *malloc(size_t numbytes);

引数の「numbytes」は割り当てたいメモリのバイト数で、戻り値は割り当てられたメモリブロックの先頭を指すポインタ。メモリ割り当てに失敗した時にはヌルポインタを返す。

動的に割り当てたメモリが必要なくなったら、メモリを開放しなければいけない。これには free() 関数を使う。

void free(void *ptr);

もっとも、プログラムが終了すると、割り当てられていたメモリは全て自動的に解放される。
例えば入出力用のバッファなんかは一時的にメモリが必要になるので、malloc() でメモリを割り当て、要らなくなったら free() で解放する。

次のプログラムは、ユーザの入力(文字列)を受け取るメモリブロックを動的に割り当て、必要がなくなったら解放している。

#include
#include

int main(void)
{
    char *p;

    p = (char *)malloc(80);

    if (!p) {
        printf("Fail to allocate.\n");
        exit(1);
    }

    printf("Input string: ");
    gets(p);
    printf("%s\n", p);

    free(p);

    return 0;
}
takatoh@nightschool $ ./sample_12_7
Input string: Hello!
Hello!

関数ポインタ

関数ポインタは、関数のエントリポイントのアドレスを保持する変数。
コンパイラは、プログラムをコンパイルするときに関数のエントリポイントを作成する。エントリポイントはアドレスなので、それを指すポインタ変数を持つことが可能だ。それが関数ポインタ。

関数ポインタを宣言するには、その関数の戻り値の型のポインタとして、関数に引数があればそれも続けて宣言する。

int (*p) (int x, int y);

優先順位の規則のため、*p はカッコで囲む必要がある。

関数のアドレスを関数ポインタに代入するには、関数名をカッコを付けずに指定する。例えば、次のような関数 sum() があるとすると:

int sum(int a, int b);

次のように代入する。

p = sum;

代入した関数を呼び出すには:

result = (*p) (10, 20);

呼び出し時にも *p をカッコで囲む必要がある。

戻り値の型と引数の数、型が同じであれば、関数ポインタを配列にすることもできる。

int (*p)[4] (int x, int y);

次のプログラムは、ユーザに2つの整数を入力させ、次に行いたい演算の番号を入力させる。そしてその結果を表示する。
ユーザの入力した番号から演算を選ぶ部分で関数ポインタの配列を使っている。

#include

int sum(int a, int b);
int subtract(int a, int b);
int mul(int a, int b);
int dev(int a, int b);

int (*p[4]) (int x, int y);

int main(void)
{
    int result;
    int i, j, op;

    p[0] = sum; /* Get address of sum() */
    p[1] = subtract; /* Get address of subtract() */
    p[2] = mul; /* Get address of mul() */
    p[3] = dev; /* Get address of dev() */

    printf("Input 2 numbers:\n");
    scanf("%d%d", &i, &j);
    printf("0: sum, 1: subtract, 2: mul, 3: dev\n");
    do {
        printf("Input number of operation:\n");
        scanf("%d", &op);
    } while ((op < 0) || (3 < op));
    result = (*p[op]) (i, j);
    printf("%d\n", result);
    return 0;
}

int sum(int a, int b)
{
    return a + b;
}

int subtract(int a, int b)
{
    return a - b;
}

int mul(int a, int b)
{
    return a * b;
}

int dev(int a, int b)
{
    if (b) {
         return a / b;
    } else {
        return 0;
    }
}
takatoh@nightschool $ ./sample_12_6
Input 2 numbers:
57
24
0: sum, 1: subtract, 2: mul, 3: dev
Input number of operation:
2
1368

#演算子と##演算子

C のプリプロセッサには2つの演算子がある。# と ## だ。

#演算子

#演算子はマクロ関数の引数を、引用符付きの文字列に変換する。

#include

#define MKSTRING(str) # str

int main(void)
{
    int value;

    value = 10;

    printf("%s: %d\n", MKSTRING(value), value);

    return 0;
}
takatoh@nightschool $ ./sample_12_5a
value: 10

13行目の MKSTRING(value) が “value” に置き換えられている。

##演算子

##演算子は2つの識別子を連結する。

#include

#define output(i) printf("%d %d\n", i ## 1, i ## 2)

int main(void)
{
    int count1, count2;

    count1 = 10;
    count2 = 20;

    output(count);

    return 0;
}
takatoh@nightschool $ ./sample_12_5b
10 20

outputマクロの引数 count が、##演算子によって 1 と 2 が連結され、count1 と count2 になっている。

組み込みマクロ

ANSI C 標準に準拠したコンパイラなら、次の5つの定義済みマクロがある。

__FILE__現在コンパイル中のファイル名
__LINE__現在コンパイル中の行番号(整数値)
__DATE__現在の日付(文字列)
__TIME__コンパイルを開始した時刻(文字列)
__STDC__コンパイラが ANSI C 標準に準拠しれいれば 1

次のプログラムは、__FILE__, __LINE__, __DATE__, __TIME__ を使った例。

#include

int main(void)
{
    printf("Compiling: file: %s, line: %d at %s %s\n",
    __FILE__, __LINE__, __DATE__, __TIME__);

    return 0;
}
takatoh@nightschool $ ./sample_12_4a
Compiling: file: sample_12_4a.c, line: 7 at Jun 13 2015 15:57:24

__DATE__, __TIME__ はコンパイルしたときに値が決まるので、プログラムをいつ実行しても同じ日付、時刻を出力する。

__STDC__ についても試してみよう。

#include

int main(void)
{
    printf("%d\n", __STDC__);

    return 0;
}

結果は:

takatoh@nightschool $ ./sample_12_4b
1

#error、#undef、#line、#pragma

#error

#error エラーメッセージ

プリプロセッサは #error ディレクティブを見つけるとコンパイルを中断する。

#include

int main(void)
{
    int i;

    i = 10;

    #error This is an error message.

    printf("%d\n", i); /* This line is NOT compiled */

    return 0;
}
takatoh@nightschool $ gcc sample_12_3a.c -o sample_12_3a
sample_12_3a.c: In function ‘main’:
sample_12_3a.c:10:2: error: #error This is an error message.
 #error This is an error message.
  ^

#error ディレクティブは主にデバッグ目的で使われる。

#undef

#undef マクロ名

#undef ディレクティブはマクロ名の定義を解除する。マクロ名が定義されていなければなんの効力もない。#undef は主にマクロ名の効果を局所的に限定するためにある。

#include

#define DOG

int main(void)
{
    #ifdef DOG
    printf("DOG is defined.\n");
    #endif

    #undef DOG

    #ifdef DOG
    printf("This line is NOT compiled.\n");
    #endif

    return 0;
}
takatoh@nightschool $ ./sample_12_3b
DOG is defined.

#line

C のコンパイラは、コンパイルの最中、現在コンパイルしているファイル名と行番号を保持している。#line ディレクティブを使うと、これらの値を変更することができる。一般的な形式は次のとおり。

#line 行番号 "ファイル名"

主にデバッグや大規模プロジェクトの管理に使用する、と本には書いてあるけど、ユースケースが思い浮かばない。

#include

int main(void)
{
    int i;

    #line 1000 "myprog.c"
    #error Check the line number and file name.

    return 0;
}
takatoh@nightschool $ gcc sample_12_3c.c -o sample_12_3c
myprog.c: In function ‘main’:
myprog.c:1000:2: error: #error Check the line number and file name.

確かに行番号やファイル名が #line ディレクティブで指定した値になっている。

#pragma

#pragma 命令

本には「#pragma ディレクティブを使うと、コンパイラに渡すプリプロセッサディレクティブを、コンパイラの実装者が定義することができます。」と書いてある。これって、コンパイラを使うプログラマには使い道がないんじゃ……。