#defineプリプロセッサディレクティブ

C のプリプロセッサはコンパイルの前にソースコードに対して様々な操作を行う。プリプロセッサへの命令がプリプロセッサディレクティブ。これまでにヘッダファイルを読み込む #include を使ってきた。
ここでもうひとつ、#define というディレクティブが出てきた。大まかに言うと、ソースコード中の特定の文字列を別の文字列に置き換えるものだ。これはマクロ置換(macro substitution) と呼ばれる。#define の一般的な形式は次のとおり。

#define マクロ名 文字列

マクロ名は C で有効な識別子なら何でもいい。大文字も小文字も使える。ただ、大文字で書くのが通常だそう。
マクロ名と文字列は1つ以上の空白文字で区切られていなければいけない。
文字列の方は途中に空白を含んでも構わない。ただし、ディレクティブ自体が1行で完結している必要があるので、行末までが有効な文字列ということになる。

次の例は MAX というマクロを定義している。プリプロセッサがこれを処理すると、for ループの中の MAX という文字列が 100 という文字列に置き換わる。

#incluce

#define MAX 100

int main(void)
{
    int i;

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

    return 0;
}

結果としてこのループは、コンパイラにはこう見える。

for (i = 0; i < 100; i++) {
    printf("%d\n", i);
} 

いったん定義したマクロはほかのマクロの中でも使える。次の例は正しい使い方だ。

#define SMALL 1
#define MEDIUM SMALL + 1
#define LARGE MEDIUM + 1 

マクロ名が二重引用符で囲まれた中に出てきた場合には、置き換えが行われない。例えば、

#define ERROR "error ocured."

とあっても、

printf("ERROR: Try again.\n")

のなかの ERROR は置き換えられない。これはちょっと注意だな。

main()の引数

C のプログラムでは、コマンドライン引数を main() の2つの引数、argc と argv で受け取る。argc と argv という名前は決まったものではないらしいけど、伝統的のこの名前が使われているらしい。
argc は int 型で、コマンドライン引数の数を受け取る。argv は文字列ポインタの配列で、コマンドライン引数自体を受け取る。注意が必要なのは、コマンドラインに打ち込んだプログラム自体も含まれているということだ。なので、argc は 1 以上の整数になり、argv[0] にはプログラム名が入っている。

これらは次のように使う。

int main(int argc, char *argv[])
{
    ....
}

*argv[] となっているのは、コマンドライン引数の数が決まっていないから。

ちょっと試してみよう。次のプログラムは、コマンドライン引数の数と、それぞれの引数を出力する。

#include

int main(int argc, char *argv[])
{
    int i;

    printf("%d\n", argc);

    for (i = 0; i < argc; i++) {
        printf("%s\n", argv[i]);
    }

    return 0;
}

実行例:

takatoh@nightschool $ ./sample_7_4a
1
./sample_7_4a
takatoh@nightschool $ ./sample_7_4a foo bar baz
4
./sample_7_4a
foo
bar
baz

さて、コマンドライン引数は文字列として渡されるので、もし数値として使いたいなら、文字列から数値へ変換しなければいけない。よく使われる関数は次のとおり。これらを使うには stdlib.h を include する必要がある。

int atoi(char *str);
double atof(char *str);
long atol(char *str);

long は long int のことね。
試してみよう。

#include
#include

int main(int argc, char *argv[])
{
    int i;
    double d;
    long l;

    i = atoi(argv[1]);
    d = atof(argv[2]);
    l = atol(argv[3]);

    printf("%d %f %ld\n", i, d, l);

    return 0;
}
takatoh@nightschool $ ./sample_7_4 2 3.5 200000
2 3.500000 200000

ところで、1つ疑問な点がる。それは「char *argv[]」という宣言だ。
最初に argv は文字列ポインタの配列だと書いた。だけどこの宣言はそうは見えない。「char ch」なら文字変数、「char str[]」なら文字の配列(文字列)。だとすると「char *argv[]」は文字列のポインタじゃないのか。それとも [] よりも * のほうが結合強度が強くて文字ポインタの配列か。どちらにせよよくわからないな。

引数の値渡しと参照渡し

int 型や float 型の引数を普通に渡すのが値渡し。このとき引数の値はコピーされて関数に渡されるので、関数の中で変更しても元の(つまり関数の外の)変数の値は変化しない。
これに対して、ポインタで渡すのが参照渡し。引数のポインタの持っているアドレスがコピーされて関数に渡されるけど、このアドレスは関数の呼び出し元のポインタと同じデータを指している。結果として、関数内で引数(ポインタ)の指すデータを変更すると、呼びさし側でもデータが変化することになる。

参照渡しの場合は、関数の仮引数をポインタとして定義する。次のプログラム中の関数 swap は2つの引数(ポインタ)をとって、相互に値を交換する。

#include

void swap(int *i, int *j);

int main(void)
{
    int num1, num2;

    num1 = 100;
    num2 = 800;

    printf("num1= %d, num2= %d\n", num1, num2);
    swap(&num1, &num2);
    printf("num1= %d, num2= %d\n", num1, num2);

    return 0;
}

void swap(int *i, int *j)
{
    int temp;

    temp = *i;
    *i = *j;
    *j = temp;
}

「void swap(int *i, int *j)」というのが、ポインタ引数の書き方だ。ここでは2つ使っている。
実行結果:

takatoh@nightschool $ ./sample_7_3
num1= 100, num2= 800
num1= 800, num2= 100

ちゃんと num1 と num2 の値が入れ替わった。

ところで、引数に配列(文字列を含む)を渡す場合、配列のアドレスが渡されるだけで配列そのものがコピーされるわけじゃない。これは参照渡しだ。だから、関数の仮引数はポインタ型として定義する必要がある。

#include

void f(int *num);

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

    f(count);

    return 0;
}

void f(int *num)
{
    int i;

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

再帰

C でも再帰関数が書ける。特別なことも必要なく、単に自分自身を呼びだせばいい。
次のプログラムは練習問題から。階乗を計算する。

#include

int fact(int n);

int main(void)
{
    int n;

    n = fact(5);

    printf("%d\n", n);

    return 0;
}

int fact(int n)
{
    if (n) {
        return n * fact(n - 1);
    } else {
        return 1;
    }
}
takatoh@nightschool $ ./sample_7_2
120

関数プロトタイプ

関数を使う前には、関数プロトタイプの宣言が必要。前にも書いたけどこんなふうに書く。

型 関数名(型 仮引数1, 型 仮引数2, ..., 仮引数N);

コンパイラはこの宣言から次の情報を得る。

  • 関数の戻り値の型
  • 仮引数の数
  • 仮引数の型

ANSI C では、歴史的経緯によりプロトタイプがなくてもいいことになっているそうだけど、関数を使う前に宣言しておくのが普通。というか常識。常に書くべし。

標準ライブラリの関数も、ヘッダファイルにプロトタイプが書かれている。使う関数によって対応するヘッダファイルを読み込む必要があるのはこのため。

関数の戻り値がない、仮引数がないときには、キーワード vold を使う。

void myfunc(int x);
int myfunc(void);

main 関数だけは特別で、プロトタイプが必要ない。

仮引数としてのポインタ

ポインタを関数に渡すこともできる。strlen() 関数に文字列の変数名を渡すと、実際にはポインタを渡していることになる。受け取る側の関数では、同じ型のポインタを受け取るように仮引数が宣言されていないといけない。

仮引数をポインタで受け取ると、そのポインタが指している変数に、関数内部からアクセスすることができる。つまり関数呼び出し時に使われた変数をその関数が変更できるということだ。strcpy() 関数などがうまく動くのはこのため。

次のプログラムは、文字列に改行をつけて出力する puts() の独自バージョン。myputs() 関数内で、ポインタを使って文字列(文字の配列)をたどっている。

#include

void myputs(char *p);

int main(void)
{
    myputs("This is a test.");

    return 0;
}

void myputs(char *p)
{
    while (*p) {
        printf("%c", *p);
        p++;
    }
    printf("\n");
}
takatoh@nightschool $ ./sample_6_7
This is a test.

練習問題6.6

ポインタへのポインタの練習。

多重間接参照をよく理解するため、ポインタへのポインタを使って整数に値を代入するプログラムを作成してください。プログラムが終了する前に、整数変数、ポインタ、ポインタへのポインタの各アドレスを表示するものとします(ポインタの値を表示するには、%p を使います)。

#include

int main(void)
{
    int i, *p, **mp;

    p = &i;
    mp = &p;

    **mp = 100;

    printf("%p %p %p\n", &i, p, mp);

    return 0;
}
takatoh@nightschool $ ./practice_6_6_1
0x7ffc70ed385c 0x7ffc70ed385c 0x7ffc70ed3860

多重間接参照

C のポインタは、別のポインタを指すことができる。これを多重間接参照と言い、最初のポインタは2番めのポインタのアドレスを保持し、2番めのポインタが変数へのアドレスを保持する。
ポインタへのポインタを宣言するには * を2つつける。

char **mp;

ポインタへのポインタが、間接的に指している値を参照するときには * 演算子を2つつける。例えば次のように。

char **mp, *p, ch;

p = &ch;     /* ch のアドレスを取得 */
mp = &p;     /* p のアドレスを取得 */
**mp = 'A'   /* 多重間接参照を使って ch に 'A' を代入 */

次のプログラムは、ポインタへのポインタを使って、 gets() で文字列を入力する。

#include

int main(void)
{
    char **mp, *p, str[80];

    p = str;
    mp = &p;

    printf("Input your name: ");
    gets(*mp);
    printf("Hello, %s!\n", *mp);

    return 0;
}

gets や printf の引数で、mp に * が1つしかついていないことに注目。これらの関数の引数には、文字列へのポインタが要求されるからだ。2つつけると文字列の最初の文字の値になってしまう。

takatoh@nightschool $ ./sample_6_6
Input your name: Andy
Hello, Andy!

ポインタの配列

ポインタも配列にすることができる。次のようにすると、10個の要素を持った整数ポインタを宣言できる。

int *pa[10];

配列要素のポインタに、変数のアドレスを代入するには & を使う。

pa[8] = &i;

pa はポインタの配列なので、その要素が保持できる値は変数(この場合は int 型)のアドレスだけだ。pa の3番目の要素が指す整数に値を代入するには次のように * を使う。

*pa[2] = 100;

文字列定数とポインタ

コンパイラは文字列定数(二重引用符で囲った文字列)を見つけると、それを文字列テーブルに格納し、その文字列定数へのポインタを生成する。なので、ポインタに直接文字列定数を代入することができる(実際には文字列定数を指すポインタが代入される)。

#include

int main(void)
{
    char *p;

    p = "One Two Three\n";

    printf(p);

    return 0;
}
takatoh@nightschool $ gcc sample_6_4.c -o sample_6_4
sample_6_4.c: In function ‘main’:
sample_6_4.c:10:5: warning: format not a string literal and no format arguments [-Wformat-security]
     printf(p);
     ^
takatoh@nightschool $ ./sample_6_4
One Two Three

なんか warning が出てるな。結果はちゃんと期待どおりのものが出力されてるけど。
フォーマットが文字列リテラルじゃなくて引数がないってこと?
次のように直したら warning がでなくなった。

#include

int main(void)
{
    char *p;

    p = "One Two Three\n";

    printf("%s", p);

    return 0;
}
takatoh@nightschool $ gcc sample_6_4a.c -o sample_6_4a
takatoh@nightschool $ ./sample_6_4a
One Two Three