C/C++ - 言語仕様編 第2回 〜ポインタとポインタ変数〜

ポインタ変数の機能

さて、第1章で定義したポインタ変数だけど、ぶっちゃけてしまうと、その機能、使い道は3つ、 正確に言おうとすれば、3+αってことになるのかな?

それぞれ、以降で、参照子・動的変数・反復子って呼ぶことにするんで、慣れてほしい。

まぁ、これから先も何度も言うことだから、最初に言っておくけど、 C言語におけるポインタはこの3+αの機能すべてを有するっていう構造であり、 それぞれを、きっちりと区別して用いなくてはならない。用いなくちゃあならんのに、 そう使うことができるとこが、C言語らしさであり、危なげなところでもある。

参照子と引数

ポインタ変数を、ある変数のポインタ、またはポインタ変数で初期化したものである。まんまだな。

型 T の変数 t に対して、参照子 p は以下で宣言される。

T *p=&t;

参照子 p に対して、*p は変数 t として機能する。要するに、p は t を参照する。 変数 t は、値とポインタを持つわけだけど、p は、t のポインタのみを値として持つ。

さて、参照参照とはいうが、大きく分類すると4つの領域を参照することができる。 静的変数の記憶領域である、静的領域、自動変数の記憶領域である、スタック領域、 関数の記憶領域である、プログラム領域の3つと、その他である。

さりげなく言ったように、関数の記憶領域も参照子は参照することができる。 要するに、関数もまたポインタを持っているわけだ。

値を返してきたり、型を持ってたり、もちろんシンボルもポインタもあるわけで、なんとなく変数っぽい気も しなくもないこともないけど、そんな気はしない。

で、参照子の使い道としてその大半を占める使い方が、2つ目の関数の使い方である。 要するに、関数の引数として参照子を用いるものである。

1つ目の使い方では、あくまで、関数の定義内のスコープは呼び出し元とは完全に独立していた わけだが、参照子をもってくることで、呼び出し元スコープにあるはずの変数の値を変化させる ことができる。

void f(S *s);

としたような関数に対して、型Sの変数s1を、f(&s1); として与えると、 s は、ポインタ &s1 で初期化された参照子として機能する。

関数 f の定義内で、本来別のスコープ内にあるはずの、s1 の値を書き換えることができ、 このように、スコープ外の値を書き換えてしまう関数を、副作用を持つって言うんだな。

もう一度まとめると、関数には、ある初期化値を与えることで、新しく値を作り出す使い方と、 ある参照子を経由して、呼び出し元スコープの値を書き換える使い方の2つがあるわけだ。

参照子は、とにもかくにも宣言と同時に、ポインタで初期化しなくちゃあならんわけで、 関数の引数として用いると、関数の呼び出しと同時に初期化ができるって寸法なわけだな。

動的変数とヒープ領域

参照子が参照できる領域の4つ目をその他って思いっきりくくっちゃったけど、 要するに、その他ってのがヒープ領域である。

その生存期間が最初から最後までである静的領域とプログラム領域、 各スコープのみに限定されるスタック領域と違って、ヒープ領域はその生存期間が管理されない。 要するに、自由に管理するだけの余地がある、つまり、生存期間が動的なわけだな。

型 T とする名前 t の動的変数の定義は、#include <stdlib.h> として、ヘッダファイルをインクルードした後に、

T *t=(T *)malloc(sizeof(T));

である。

ポインタ変数・関数・キャストオペレータ・sizeofオペレータが一気に出てきたわけだが、 とにもかくにも、このようにして定義したポインタ変数 t が、動的変数なわけだな。

動的変数の生存期間は、この void *malloc(size_t) 関数を用いて初期化した瞬間から、 void free(void *)関数を用いて、

free(t);

とするまでの期間である。

生存期間が動的なわけで、自動変数のように、その生存期間をスコープに縛られずにすむ。 free()関数は必ず、動的変数を引数にとって、1回実行しなくてはならない。 0回でも2回でもなく、ただ1回である。

繰り返すようだけど、動的変数を表すポインタ変数と参照子を表すポインタ変数は共用できない。 free(void *)の引数として使えるのは、動的変数のみである。

反復子と配列

void *calloc(size_t,size_t)関数は、malloc()関数と同様に、ヒープ領域から記憶領域を取るものだが、 引数を2つとる。第2引数はmalloc()と同じく、sizeof(T)を入れるわけだが、 第1引数は、その数を入れることができる。

要するにまぁ、

T *t=(T *)calloc(n, sizeof(T));

とすると、n個のT型の動的変数が連続的に並ぶようにヒープ領域から記憶領域を確保する。

T *t=(T *)malloc(n*sizeof(T));

としてるのと感覚的には同じだけど、calloc()関数はさらにメモリを0でクリアしてくれる。

C言語って、自動変数を初期化なしに宣言すると、未定義値となる。 普通の言語って、未定義値って値が定義されているっていうなんか、すんごく矛盾してるっぽいこと やってるんだけど、C言語は、その名のとおり、未定義である。何が入ってるか定義されていない。漢だな。 

malloc()も、そういう立場で、基本的にヒープ領域からとってきたとこにどんな値が記録されているかは、 まったくもって、未定義である。

ある自然数リテラルmに対して、

T *t=(T *)malloc(m*sizeof(T));

と、まぁ、ほっとんど同じことを、

T t[m];

で実現することができる。まぁ、いまさらだけど、こういう風に、ある型Tの変数の記憶領域を複数連続して とったデータ記憶領域を表す変数を、配列って呼ぶんだな。

この2つの違いは、前者が動的変数による配列、後者が自動変数による配列ってことである。 つまりは、前者はfree()が必要であり、後者はスコープを抜けると勝手に破壊されるってことである。

どっちでもいいけど、とにかく、こうやって定義した t に対して、反復子 itr は以下で宣言される。

T *itr=t;

反復子は、一瞬参照子と全く同じことをしてるように見えるけど、参照子と決定的に違う点がある。 参照子と動的変数を表すポインタは、原則として、初期化してしまったが最後、 よっぽど特殊な事情がない限り、まぁ、はっきり言えば、使いたくなくなるそのときが来るまで代入してはだめである。

・・・まぁ、代入できちゃうあたりがC言語らしいんだけど。

反復子は、そんな鬱陶しい制約を完全にとっぱらった、もっともポインタ変数らしいポインタ変数である。

いつでもどこでも代入自由であり、整数リテラル m を用いて、加算オペレータや減算オペレータを用いて、 itr+m としたり、itr-m としたり、インクリメントオペレータ・デクリメントオペレータを用いて、 ++itr, itr-- としたりと、もう、やりたい放題し放題の、なんでもありである。

唯一の制約は、*オペレータが使用可能なのは、そのときの反復子が持つポインタに対応する 記憶領域が存在しているときのみである、と、ただそれだけである。

tがT型の配列であるとき、T *itr=t; と初期化した反復子と自然数mに対して、

*(itr+n);

は、全く同じことを、

t[n]; 

として表現可能である。たとえば、tが、

T t[N];

として宣言された T [N]型配列であるとすると、t[0]からt[N-1]までが有効となる。 つまり、*(itr+N-1)までは有効で、*(itr+N)はどうなるかわからんということである。

さりげなく書いたけど、T []型のシンボル t は、配列tの先頭要素に対する参照子である。 添字オペレータ[]をはじめとして、T []型の配列は、要するに、T *型のポインタ変数の シンタックスシュガーなわけだな。

ところで、インクリメントオペレータは、自分自身に1を加算するものである。

++itr;

とやると、これは、

itr=itr+1;

を表してることになる。

たとえば、T [N]型配列tがあるとき、反復子 T *itr, *begin, *endに対して、

begin=t;
end=t+N;
for(itr=begin;itr!=end;++itr)...

などと使うことで、反復子itrはループ内で配列の全要素を走査することができる。 とまぁ、このような反復の制御構文内で使うのが反復子の基本的な使い方である。

汎用変数

malloc()関数やfree()関数でさりげなく使ってきた、void *というポインタ変数型だが、 これが、C言語におけるポインタの第4の使い方である。 

代入オペレータは、キャストを用いることなく右辺と左辺で異なる変数を代入することは原則できないが、 void *型ポインタ変数には、ありとあらゆるT *型ポインタ変数を代入可能であり、 void *型ポインタ変数は、ありとあらゆるT *型ポインタ変数に対して再解釈キャスト可能である。

このような機能を持つ型は、void *型ただ1つである。

とまぁ、ここまでがC言語におけるポインタのすべてといっても過言ではない。 あとは、ここまでに述べて3+αのポインタ変数の使い方を、これまでT型としていたものを、 実際の型におきかえていくだけである。


文章作成 : yukki-ts (-+-twilight serenade-+- [stage])