仮引数としてのポインタ

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

[追記]

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

ポインタの演算

ポインタは、整数の加算と減算だけができる。

int *p;

...

p = p + 1;

ポインタの加算・減算は整数のそれと意味が異なる。ポインタはデータのアドレスを格納しているので、ポインタに 1 加算すると、次のデータを指すようになる。減算の場合は逆だ。

#include

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

    p = ary;

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

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

    return 0;
}
takatoh@nightschool $ ./sample_6_2a
1
2

上の例では、9行目で p に配列 ary のアドレスを代入している。この時点で p は ary の先頭の要素を指しているので、11行目の出力では、1 が出力されている。
その後、p に 1 加算すると、今度は ary の2番目の要素を指すようになる。なので、14行目の出力では 2 が出力されている。

ポインタの演算には、インクリメント演算子・デクリメント演算子も使える。

p++;
p--;

整数のバイト同じく 1 加算したり減算したりするんだけど、上に書いたように制すのものとは意味が違う。

インクリメント演算子・デクリメント演算子を間接参照と一緒に使うときには注意が必要だ。次のように書くと、ポインタ自体がインクリメントされたあと間接参照が行われる。

*p++;

参照しているデータをインクリメントしたいときにはカッコをつける。

(*p)++;

次のプログラムでは、最初の出力ではポインタ自体がインクリメントされて配列の2番めの要素を指すようなった結果 20 と出力され、2番めの出力ではポインタの指している配列の要素がインクリメントされて 21 と出力される。

#include

int main(void)
{
    int narray[] = {1, 20, 300, 4000};
    int *p = narray;

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

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

    return 0;
}
takatoh@nightschool $ ./sample_6_2b
20
21

ポインタ

ポインタは別の変数(データ)のアドレスを保持する変数。p という変数(ポインタ)が別の変数 q のアドレスを保持しているとき、「p は q を指している」という。

ポインタの宣言

ポインタを宣言する、一般的な形式は次の通り。

型 *変数名;

ポインタは変数名の前に * (アスタリスク)をつけて宣言する。

ポインタ演算子

* と & の2つのポインタ演算子がある。両方とも変数名の前につけて使う。* はポインタの指しているデータの値を返す。& は変数のアドレスを返す。
次のプログラムは、int 型のポインタ p と変数 q を使っている。

#include

int main(void)
{
    int *p, q;

    q = 199;
    p = &q;

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

    return 0;
}

9行目で q のアドレスを p に代入し、11行目で p を使って q の値を表示している。ポインタを通じて変数の値を参照することを「間接参照(indirection)」という。結果として、199 と表示される。

takatoh@nightschool $ ./sample_6_1a
199

間接参照は変数に値を代入するのにも使える。

int *p, q;

p = &q;
*p = 199;

これで q に 199 が代入された。

ポインタとデータ型

型は重要だ。これによって、コンパイラはポインタの指しているデータがどれくらいのメモリ領域を使うかを知ることができる。
次のプログラムは、int 型のポインタを使って double のデータを代入しているため、正しく出力しない。

#include

int main(void)
{
    int *p;
    double q, temp;

    temp = 1234.56;

    p = &temp;
    q = *p;

    printf("%f\n", q);

    return 0;
}
takatoh@nightschool $ gcc sample_6_1b.c -o sample_6_1b
sample_6_1b.c: In function ‘main’:
sample_6_1b.c:11:7: warning: assignment from incompatible pointer type [enabled by default]
     p = &temp;
       ^
takatoh@nightschool $ ./sample_6_1b
1889785610.000000

文字列の配列

文字列の配列は、しばしば文字テーブルと呼ばれる。次の例は、(ヌル文字を含めて)40文字の文字列を10個格納できる。

char names[10][40];

各文字列にアクセスするには、左の添字だけを書いてやる。例えばキーボードから読み取った文字列を3番目に格納するには、

gets(names[2]);

とする。同様に、5番目の文字列を出力するには、

printf(names[4]);

とする。

次のプログラムは、文字テーブルに 0 から 9 を意味する単語を格納しておき、ユーザーが入力した数値に対応する単語を出力する。

#include

int main(void)
{
    char digits[][10] = {
        "zero",
        "one",
        "two",
        "three",
        "four",
        "five",
        "six",
        "seven",
        "eight",
        "nine"
    };
    char num[2];

    printf("Input a number: ");
    gets(num);
    printf("%s\n", digits[num[0] - '0']);

    return 0;
}

実行例:

takatoh@nightschool $ ./sample_5_5
Input a number: 3
three

配列の初期化

配列も初期化することができる。一般的な形式は次のとおり。

型 変数名[サイズ] = {値のリスト};

「値のリスト」は、定数をカンマで区切ったもの。当然だけどサイズよりも少なくなければいけない。例えば次のように。

int ary[5] = {1, 4, 9, 16, 25};

2次元の場合にも同様に初期化できる。

int sqr[3][3] = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

文字列の初期化には2通りのやり方がある。1つは、1文字ずつカンマで区切って初期化する方法。この方法だとヌル文字を自分で追加する必要がある。次の例ではヌル文字を追加していない。

char str[3] = {'a', 'b', 'c'};

もう1つは、二重引用符で囲んだ文字列で初期化する方法。この方法だとコンパイラが自動的にヌル文字を追加するので、配列の大きさを初期化しようとする文字列よりも大きくしておく必要がある。

char str[5] = "Herb";

1次元の配列を初期化する場合は、配列のサイズを指定しなくても構わない。次のようにすると、コンパイラが要素数を数えて配列のサイズを決めてくれる。

int pwr[] = {1, 2, 4, 8, 16, 32, 64, 128};

サイズを明示しない配列を、「サイズ指定のない配列(unsized array)」と呼ぶ。
何かの都合で、初期化する要素数が変わっても、数えなおさなくていいので便利。

サイズ指定のない配列は2次元以上の場合には、最初の次元だけサイズを省略することができる。逆に言うと、2次元以上のサイズは省略が出来ない。

int sqr[][3] = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};