ポインタ

「C言語は難しい!」なぜなら「ポインタがあるから」といわれる程、ポインタはC言語を修得するための一つの山であるのは間違いなさそうです。しかしポインタを通して学ぶ関連知識は、Javaなどのオブジェクト指向言語の修得にも通じるものですから得るものも大きいです。相手にとって不足無し!頑張っていきましょう!!


ポインタの概要

コンピュータはプログラム内の変数をメモリー上に確保し、その場所を特定するためのアドレスを記憶しています。プログラムでそのアドレスを取り扱いたいときに使用するのがポインタです。つまり、ポインタとはアドレスを取り扱う変数のことです。

ポインタイメージ

▲PageTop

ポインタの構文

ポインタの構文は次の通りです。


ポインタの宣言

データ型 *ポインタ変数名;

(例)int型変数のポインタを宣言するとき。

int *p;

アドレスを代入

変数に”&”をつけるとそのアドレスが取得できます。

&変数名;

・サンプルソース(sample0701.c)

#include <stdio.h>

int main() {
    int x = 10;
    int *p;
    p = &x;  /* アドレスの代入。ちなみに宣言と同時にアドレスを代入することもできます。int *p = &x; */
    printf("「&x」のアドレス = %p\n", &x);
    printf("「p」が示すアドレス = %p\n", p);

    return 0;
}

・実行結果

C:\dev\c>sample0701 [Enter]
「&x」のアドレス = 0012FF88  ← 実行環境によって表示されるアドレス値は変わります。
「p」が示すアドレス = 0012FF88
        

ポインタが示すアドレス内の値を表示

ポインタに”*”をつけるとポインタが示すアドレス内の値が取得できます。

*ポインタ変数名;

・サンプルソース(sample0702.c)

#include <stdio.h>

int main() {
    int x = 10;
    int *p = &x;
    /* 変数xのアドレスを代入したのでxの値が表示されるはず */
    printf("「p」が示すアドレス内の値 = %d\n", *p);

    return 0;
}

・実行結果

C:\dev\c>sample0702 [Enter]
「p」が示すアドレス内の値 = 10  ← xの値が表示された。
        

注意点

この段階で注意が必要かなと思うところを上げておきます。
1.”*”の解釈について

ポインタの宣言時にアドレスを代入する場合、先の例では「int *p = &x; 」となって「*p」に「&x」を代入したように見えますが、意味的には宣言時以外でそうしているように「p = &x」となります。ですから「*p」で取得する値はアドレスではなく、アドレス内の値となります。このことから、宣言時の代入を次のように書く人もいます。

int* p = &x;

これで宣言時も「p」に「&x」を代入しているように見えますが、この場合は次のような複数のポインタ変数を宣言するとき注意が必要です。


int* p1, p2;

上記はポインタ変数p1,p2を宣言しているのではなく、ポインタ変数p1とint型変数p2を宣言していると解釈されます。


「ポインタ変数の宣言」と「アドレス内の値の取り出し」を別の記号であればわかり易いとおもうのですが。


2.ポインタの初期化

ポインタ変数を宣言しただけでは、そのポインタ変数が示すアドレスは不定です。ですからポインタ変数を初期化(安全な領域のアドレスを代入)しないで使用すると不特定のアドレスにアクセスすることになり、システム破壊につながる深刻なバグになります。(コンパイラが注意してくれると思いますが。)

#include <stdio.h>

int main() {
    int x = 10;
    int *p;
    *p = x;  /* pを初期化(p = &x;)していません! 重大なバグです!! */
    printf("「p」が示すアドレス内の値 = %d\n", *p);

    return 0;
}

▲PageTop

配列とポインタ

配列の先頭アドレスをポインタ変数に代入する事によりポインタ演算を使って配列を取り扱う事が出来ます。


・サンプルソース(sample0703.c)

#include <stdio.h>

int main() {
    int *p, i, data[5] = {10,20,30,40,50};
    p = data;  /* 1. 配列の先頭アドレスをポインタ変数に代入。 */
    for(i=0; i<5; i++) {
        printf("%d\n", *(p + i));  /* 2. ポインタを要素数分進めて値を取り出す。 */
    }

    return 0;
}

・実行結果

C:\dev\c>sample0703 [Enter]
10
20
30
40
50
        

上記サンプルを元に配列とポインタの関係を整理します。


1. 配列の先頭アドレスをポインタ変数に代入。
「p = data」(5行目)これにより配列の先頭アドレスをポインタ変数に代入しています。つまり次の関係が成り立ちます。
配列変数data[]において
&data[0]はdataと同じである。

2. ポインタを要素数分進めて値を取り出す。
「*(p + i)」(7行目)で配列の要素が取り出せます。つまり次の関係が成り立ちます。
配列変数data[]、ポインタ変数p、int型変数iにおいて「p = data」のとき
*(p + i)はdata[i]と同じである。

▲PageTop

関数のポインタ

関数もメモリ上に登録されており、そのアドレスを示すポインタ(関数ポインタ)を使用すれば関数を呼び出す事が可能です。関数ポインタの構文は次の通りです。


関数ポインタの宣言

戻り値の型  (*関数ポインタ名)(引数リスト);

また宣言と同時に関数のアドレスを代入(初期化)する事も出来ます。

戻り値の型  (*関数ポインタ名)(引数リスト) = 関数のアドレス;

※関数のアドレスは関数から「()」(関数呼出し演算子)を省いた関数名で取得できます。

関数「func()」において「func」は関数funcのアドレスを表します。

関数ポインタの使用

関数ポインタの使用例は次のようになります。


・サンプルソース(sample0704.c)

#include <stdio.h>

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

int main() {
    int (*pfunc)(int, int) = add;
    printf("%d\n" , pfunc(1, 1));

    return 0;
}

・実行結果

C:\dev\c>sample0704 [Enter]
2
        

関数ポインタ配列

関数ポインタは配列にする事も可能です。宣言は次のようになります。


戻り値の型  (*関数ポインタ名[要素の数])(引数リスト);

宣言と同時に初期化するとき。

戻り値の型  (*関数ポインタ名[要素の数])(引数リスト) = { 関数のアドレス, 関数のアドレス, ・・・ };

・サンプルソース(sample0705.c)

#include <stdio.h>

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

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

int main() {
    int i;
    int (*pfunc[2])(int, int) = {add, sub};
    for(i=0; i<2; i++) {
        printf("%d\n" , pfunc[i](1, 1));
    }

    return 0;
}

・実行結果

C:\dev\c>sample0705 [Enter]
2
0
        

▲PageTop