仮引数としてのポインタ

ポインタを関数に渡すこともできる。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

配列とポインタ

配列は、データがメモリ上に順に並んでいるので、ポインタと相性がいい。前のエントリでさらっと書いたけど、配列名を添字を付けずに使うと、先頭を指すポインタを返す。

int a[10];
int *p;

p = a;

配列の要素にポインタでアクセス

配列の各要素には、添字を使ってアクセスするほかにポインタを使ってもアクセスできる。

#include

int main(void)
{
    int a[10] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
    int *p;

    p = a;

    printf("%d %d %d\n", a[0], a[1], a[2]); /* access with index */
    printf("%d %d %d\n", *p, *(p + 1), *(p + 2)); /* access with pointer */

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

2次元配列

2次元配列の場合は、ポインタを型キャストしてやる必要がある。これはよくわからないな。次のような float 型の2次元配列の要素にアクセスするには、float 型のポインタでいいんじゃなかろうか。

#include

int main(void)
{
    float balance[5][5] = {
        { 1.0, 2.0, 3.0, 4.0, 5.0},
        { 6.0, 7.0, 8.0, 9.0, 10.0},
        {11.0, 12.0, 13.0, 14.0, 15.0},
        {16.0, 17.0, 18.0, 19.0, 20.0},
        {21.0, 22.0, 23.0, 24.0, 25.0}
    };
    float *p;

    p = balance;

    printf("%f\n", *p);
    printf("%f\n", *(p + 1));

    return 0;
}

これを試してみると:

takatoh@nightschool $ gcc sample_6_3b.c -o sample_6_3b
sample_6_3b.c: In function ‘main’:
sample_6_3b.c:15:3: warning: assignment from incompatible pointer type [enabled by default]
 p = balance;
   ^
takatoh@nightschool $ ./sample_6_3b
1.000000
2.000000

出力結果は予想どおりだけど、コンパイル時に warning が出ている。互換性のないポインタを代入している、ということらしい。でもちゃんと動いてるんだけど…。

ポインタへの代入を次のように直すと warning は出なくなった。

#include

int main(void)
{
    float balance[5][5] = {
        { 1.0, 2.0, 3.0, 4.0, 5.0},
        { 6.0, 7.0, 8.0, 9.0, 10.0},
        {11.0, 12.0, 13.0, 14.0, 15.0},
        {16.0, 17.0, 18.0, 19.0, 20.0},
        {21.0, 22.0, 23.0, 24.0, 25.0}
    };
    float *p;

    p = (float *) balance;

    printf("%f\n", *p);
    printf("%f\n", *(p + 1));

    return 0;
}
takatoh@nightschool $ gcc sample_6_3c.c -o sample_6_3c
takatoh@nightschool $ ./sample_6_3c
1.000000
2.000000

ポインタの添字

ポインタが配列を指しているときに限って、ポインタに添字をつけることができる。

#include

int main(void)
{
    char str[] = "Pointer is interesting.";
    char *p;
    int i;

    p = str;

    for (i = 0; p[i]; i++) {
        printf("%c", p[i]);
    }
    printf("\n");

    return 0;
}
takatoh@nightschool $ ./sample_6_3d
Pointer is interesting.

配列のアドレスを使う

最初に書いたように、配列名を添字を付けずに書くとその配列の先頭を指すポインタを返す。なのでこれを普通のポインタの代わりに使うこともできる。

#include

int main(void)
{
    char str[80];

    *(str + 3) = 'c';

    printf("%c\n", *(str + 3));

    return 0;
}
takatoh@nightschool $ ./sample_6_3e
c

[追記]

コメントをもらった。配列名を添字なしで使ったときにポインタのように振る舞うのは、暗黙の型変換が行われるからだそう。配列名の型自体はあくまで配列だと。
ふーむ、なるほど。でも、参照するときに限って言えば、暗黙の型変換のおかげで実質的にポインタとして(というかポインタの代わりに)使えるってことでいいのかな。