[前へ]   [目次へ]   [次へ]

ポインタ(1)(C/C++)


今回はポインタについてです。
C/C++のある種の代名詞のような気もするポインタですが、
概念的には別にC/C++固有のものというわけではなく、大抵の言語に存在しています。

確かにC/C++においてはポインタの重要性は非常に高く、
ポインタなしではあまり大した事ができないのは間違いありません。

特に、C/C++でのポインタは関数の活用に必須な要素となっているので、
ポインタがないと関数の価値もほとんどなく、逆もまたそうです。


さて、そろそろ本題に入りたいと思います。

まずあっさり言ってしまえば、「ポインタ」というのは今までこの講座で「実体ID(絶対)」と呼んでいたものです。
これ以降、「実体ID(絶対)」はちょっと略して「絶対ID」と書きます。
そして「ポインタ型」とは「絶対IDを格納できる型」です。

簡単な例を見てみましょう。
<  1>
<  2>
<  3>
<  4>
<  5>
<  6>
<  7>
<  8>
<  9>
< 10>
< 11>
< 12>
#include <stdio.h>

int main(void){
   
int n=10;
   
int *p;//(1)ポインタ型変数定義
   
   
p=&n;//(2)pにnの絶対ID代入
   
printf("%p=%d\n",p,*p);//(3)pの値とpの値を使っての実体参照
   //終了待ち
   
getchar();
   
return 0;
実行例:
0013FF78=10

(1)5行目は、 int* 型の変数 p を定義しています。
(構文については 第6回 を参照してください。)
ポインタ型の変数と普通の変数とはいくつかの相違点があります。

ポインタ型のポイントとしては、
●1.「絶対ID」を1個のポインタ型変数に1個格納することが出来ます。
●2.算術演算子のほとんどは異なる動きをするか、使えません。
●3.ポインタ型にしか使えない演算子があります。
●4.全ての型にはポインタ型が存在しています。(ポインタ型にもまた、ポインタ型がある)
●5.ポインタ型のサイズは「絶対ID」のサイズなので、元の型の大きさは関係ありません。
という点に注意すると理解しやすいと思います。

1.については通常の変数同様、一個の値を保持します。

2.は、 = 代入演算子のみが普通の型と同様で、加減算系( + - ++ -- += -= )は動作変更、
他の算術演算子は使うとエラーになります。
なお、論理演算子系は通常通り、ビット演算子は禁止、その他分類はいろいろです。

3.は、「ポインタ参照系」演算子( * -> )です。
これらの演算子は「ポインタ型に格納されている絶対ID」の実体を呼び出します。

4.は読んで字のごとくです。ポインタ型は「?型の実体を示す絶対ID」を格納するものなので、
絶対IDを持つものは全て、ポインタ型を作成できます。

5.もそのままです。絶対IDは一意なので型は関係ありません。


この時点までを実行した時の状況の一例
実体ID(絶対)実体の型保持値所属
0x0013FF78int10main(n,line 4)
0x0013FF7Cint*不定(未初期化)main(p,line 5)
mainブロック 確保位置ID:0x0013FF78

これより、上記の「実体状態一覧表」を用います。
この表中の色分けは以下の通りです。
   通常部分=黒
   実体の内容が更新された=赤
   実体の内容が処理の中継に使用された(主にポインタ)=青
   実体の内容は参照された=緑
   実体の内容は更新される可能性があったが、何も起こらなかった=灰色



(2)7行目は、 p に n の絶対IDを代入しています。

A B C D
p = & n


通し記号種別
Aint*不定(未初期化)変数(p)
B演算子(=)(2項、算術、優先2、結合←)
C演算子(&)(単項、その他、優先15、結合なし)
Dint10変数(n)
優先順位から、演算子C(アドレス取得、目標 変数D)から処理されます。

& (アドレス取得)演算子は、「目標の型へのポインタ型として、目標の絶対IDを得る」演算子です。
この場合、目標は int 型変数の n です。
なので、「 int 型へのポインタ型である int* 型で、変数 n の絶対ID」になります。

A B C
p = vtemp1


通し記号種別
Aint*不定(未初期化)変数(p)
B演算子(=)(2項、算術、優先2、結合←)
Cint*0x0013FF78一時変数(参照先:n)
これは今まで通り、普通に代入されます。


この時点までを実行した時の状況の一例
実体ID(絶対)実体の型保持値所属
0x0013FF78int10main(n,line 4)
0x0013FF7Cint*0x0013FF78(main::n)main(p,line 5)
mainブロック 確保位置ID:0x0013FF78
これにより、ポインタ型変数 p には変数 n の絶対IDが格納されました。


(3)8行目は、ポインタ型変数 p を使って変数 n の値を表示しています。
printf 関数の変換指定には初出の %p が使われていますが、
これは「ポインタ型の値を表示する」という変換指定です。
どのような書式かは規定されていませんが、通常は16進数で表示されます。

A      B C           D   E F B'
printf ( "%p=%d\n" , p , * p )


通し記号種別
Aint(*)(const char*,...)0x00401400関数(printf)
B演算子( () )(その他、優先16、結合→)
Cconst char[7]"%p=%d\n"文字列定数
Dint*0x0013FF78変数(p)(参照先:n)
E演算子(*)(単項、その他、優先15、結合なし)
Fint*0x0013FF78変数(p)(参照先:n)
B'演算子Bの終点
優先順位から、演算子B(関数呼び出し、引数 C〜F)から処理されます。
しかし、E〜Fが式になっているのでまずはこちらが先行されます。

E F
* p


通し記号種別
E演算子(*)(単項、その他、優先15、結合なし)
Fint*0x0013FF78変数(p)(参照先:n)
というわけで、演算子E(ポインタ参照、目標 変数F)から処理されます。

* (ポインタ参照)演算子は、
「目標のポインタ型変数が示す型で、目標が保持する絶対IDの実体への参照」を作ります。
これはちょうど、 & アドレス取得演算子とは真逆の動作となります。
つまり、
「 & アドレス取得演算子を使った数だけ * ポインタ参照演算子を使うと元に戻る」
ということです。

また、このとき
「絶対IDのみを用いているので関数などのネームテーブルを完全に無視する」
という点も重要ポイントとなります。
この点は次回以降で詳しく見ていきます。


さて、E〜Fの式を処理して続きです。

A      B C           D   E      B'
printf ( "%p=%d\n" , p , vtemp1 )


通し記号種別
Aint(*)(const char*,...)0x00401400関数(printf)
B演算子( () )(その他、優先16、結合→)
Cconst char[7]"%p=%d\n"文字列定数
Dint*0x0013FF78変数(p)(参照先:n)
Eint&10一時変数(参照先:n(0x0013FF78))
B'演算子Bの終点
これで後は printf 関数が呼び出されるだけです。
この printf 関数の出力は冒頭の通り、
0013FF78=10
となります。
これはポインタ型変数 p が保持する絶対IDと、
ポインタ型変数 p が保持する絶対IDを持つ実体である n の内容が表示されています。


今回の内容はポインタに関する文法事項程度なので、
まだ良くポインタの使い道は分からないかもしれません。

次回からは実際に役に立つ用法を見ていく予定です。

[前へ]   [目次へ]   [次へ]

プログラミング講座 総合目次

最終更新 2008/10/17