C言語/ポインタ

出典: フリー教科書『ウィキブックス(Wikibooks)』
ナビゲーションに移動 検索に移動

ポインタの基本[編集]

メモリアドレス[編集]

変数が宣言されると、メモリ上に領域が確保され、その領域にデータが保存される。 その保存される場所をメモリアドレスという。

メモリアドレスはバイト単位で数えられる。 1バイトは一般に8ビットで、10進数で0~255の値が格納でき、それより大きな値は複数のバイトを使う。

変数のメモリアドレスを表すには、次のように、変数名の前に&演算子(アドレス演算子)を付ける。

&変数名

たとえば

&a

なら、変数aのアドレスである事を意味する。


コード例
//例 変数のアドレスを表示する。
#include <stdio.h>

int main(void)
{
	int a=1234;
	printf("変数aの値は%d\n", a);
	printf("変数aのアドレスは%p\n", &a);
}

上の例では変数aの値とアドレスを表示する。

実行結果
変数aの値は1234
変数aのアドレスは0023FB38 //実際の値は処理系によって異なる。

と表示される。 これはメモリアドレス0023FB38に、値1234が格納されていることを表す。

ポインタの宣言[編集]

ポインタとは、変数のアドレスを格納するための領域である。

    int *p

のようにすると、ポインタ pを宣言したことになる。

けっして、整数変数pは宣言していない(×)。

このコード

    int *p

は、格納する対象のアドレスの名前については、いっさい、言及していない。

つまり、ポインタ「*p」に変数qのアドレスや変数rなどのアドレスを格納しても、原理的には、かまわない。(ただし、まぎらわしいので、普通のプログラマーは、そういう事はしない。)

「int *p」は、けっして、変数名pのアドレスを格納するわけではない(×)。

宣言「int *p」とは、まだ、どこのアドレスを格納するかは未定だが、とりあえず、アドレスを格納するための領域(さきほどの節の例でなら 0023FB38 のような値を記憶するための領域)の名前が「p」なだけである。

そして、このようなアドレスを格納するための領域のことをポインタという。


上記コードのように冒頭に「*」をつける事で、C言語はポインタの宣言である事を認識する。

この場合の「int」とは、int型の変数を格納するためのアドレスの値(さきほどの節の例でなら 0023FB38)を格納するための記憶領域という意味である。

ポインタの名前は、べつに「p」でなくてもいい。ポインタの名前は「q」でもいい、「pA」でもいいし、「pt」でもいい。

たとえば

    int *q

なら、ポインタの名前がqである。


その他、気をつけることとして、int *p で宣言したとき、作成される変数はけっして「*p」ではなく、作成される変数はpであることに注意が必要である。したがって、int *p で作成したポインタにアドレスを代入をするときは、けっして *p ではなく、pに代入すること。

コンパイラの種類によっては、「int *P」という書き方がまるで「*P」を宣言しているように見えることをコンパイラ設計者が嫌って、「int* p」のようにint 側に*をつけて宣言する種類のコンパイラもあるが、そうではないコンパイラも存在するので仕様を確認のこと。


コード例
//例 ポインタ
#include <stdio.h>

int main(void)
{
    int i=1234;
    int *p;
    p = &i;
    printf("アドレスは%p\n",&i);
    printf("ポインタ経由で表したアドレスは%p\n",p);
}

のように使用する。

実行結果
 アドレスは0x7ffc7f3b0374
 ポインタ経由で表したアドレスは0x7ffc7f3b0374

のように表示される。アドレスの具体的な数値は、使用環境により異なる。

このように、ポインタ経由で表しても、正しくアドレスが格納されている事が分かる。



では、「int *p」ではなく、「int p」で宣言してみたら、アドレスは格納できるか? (コンパイラの種類によるかもしれないが、通常、)int p ではアドレスを格納できない。


コード例
//例 ポインタ
#include <stdio.h>

int main(void)
{
    int i=1234;
    int p;
    p = &i;
    printf("アドレスは%p\n",&i);
    printf("ポインタ経由で表したアドレスは%p\n",p);
}
実行結果
 警告: 代入でポインタからキャスト無しに整数を作成しています [-Wint-conversion]
     p = &i;
       ^

このように、エラーになってしまい、コンパイルできない。アドレスは、必ずポインタに格納する必要がある。


ポインタ経由の表示[編集]

さて、たとえばint i=1234で格納された数値1234をポインタ経由で表示したい場合には、%dなどで表示する。

//例 ポインタ
#include <stdio.h>

int main(void)
{
    int i=1234;
    int *p;
    p = &i;
    printf("アドレスは%p\n",&i);
    printf("ポインタ経由で表したアドレスは%p\n",p);
    printf("*Pを表示すると何が出るか %p\n",*p);
    printf("*Pをパーセントdで表示すると何が出るか %d\n",*p);
}
実行結果
 アドレスは0x7fff7b7cd044
 ポインタ経由で表したアドレスは0x7fff7b7cd044
 *Pを表示すると何が出るか 0x4d2
 *Pをパーセントdで表示すると何が出るか 1234

「 *Pをパーセントdで表示すると何が出るか 1234」のように、int i=1234で格納された数値1234をポインタ経由で表示したい場合には、%dなどを使用する。

つまり、

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

のようになる。



さて、&pと*pとは、全く別の値である。*p で宣言したあとの&pとは、ポインタ*pのアドレスである。(あるいは、&pとはポインタpのアドレス。pの前に「*」をつけていないことに注意。このように、文法が一貫していない。)

int *p を宣言したとき、作成される変数は「*p」ではなく、変数pが作成されることに注意が必要である。

ポインタにもアドレスが存在する。アドレスとはメモリ上の記憶領域の番号のことであるから、あるデータがメモリを使用している以上、かならず、そのデータのアドレスが存在する。


コード例
//例 ポインタ
#include <stdio.h>

int main(void)
{
    int i=1234;
    int *p;
    p = &i;
    printf("アドレスは%p\n",&i);
    printf("ポインタ経由で表したアドレスは%p\n",p);
    printf("*Pを表示すると何が出るか %p\n",*p);
    printf("アンドPを表示すると何が出るか %p\n",&p);
    printf("*Pをパーセントdで表示すると何が出るか %d\n",*p);
}
実行結果
 ポインタ経由で表したアドレスは0x7ffeecba951c
 *Pを表示すると何が出るか 0x4d2
 アンドPを表示すると何が出るか 0x7ffeecba9510
 *Pをパーセントdで表示すると何が出るか 1234

結果の3行目のアンドPの表示と、1行目のポインタ経由のアドレスの値は、異なる値になっている。


では、 *P をprintf関数の%pで表示すると何が表示されるか?

以下に実験用ソースを示す。

//例 ポインタ
#include <stdio.h>

int main(void)
{
    int i=1234;
    int *p;
    p = &i;
    printf("アドレスは%p\n",&i);
    printf("ポインタ経由で表したアドレスは%p\n",p);
    printf("*Pを表示すると何が出るか %p\n",*p);
}
実行結果
 アドレスは0x7ffeb408c8a4
 ポインタ経由で表したアドレスは0x7ffeb408c8a4
 *Pを表示すると何が出るか 0x4d2

では、&pをprintfの%dで表示するとどうなるか?

以下に実験用ソースを示す。

//例 ポインタ
#include <stdio.h>

int main(void)
{
    int i=1234;
    int *p;
    p = &i;
    printf("アドレスは%p\n",&i);
    printf("ポインタ経由で表したアドレスは%p\n",p);
    printf("*Pを表示すると何が出るか %p\n",*p);
    printf("アンドPをdで表示すると何が出るか %d\n",&p); // %dに書き換えました
    printf("*Pをパーセントdで表示すると何が出るか %d\n",*p);
}
実験結果
 アドレスは0x7ffed371646c
 ポインタ経由で表したアドレスは0x7ffed371646c
 *Pを表示すると何が出るか 0x4d2
 アンドPをdで表示すると何が出るか -747543456
 *Pをパーセントdで表示すると何が出るか 1234

ポインタとアドレスの目的[編集]

では、そもそも、なんのために、アドレスを媒介して、操作を行うのでしょう?

理由はいろいろと考えられますが、ほとんどの場合、今いる「関数」の外側にある別関数のもつ変数を操作をしたい場合です。

C言語では、複数の関数をもつプログラムでは、それぞれ関数内の変数には、たとえ変数名が同じでも、関数ごとに別のアドレスが割り当てられます。こうすることにより、あやまって他の関数の内容を書き換えてしまうことを防止しているわけです。

ですが、時と場合によっては、他の関数の内容を書き換える必要がある場合もあります。

たとえば、ある関数Aにある2つの変数xとyを交換するための別関数Bでは、原理的に、関数Bは関数Aを書き換える必要があります。


そこで、このように別関数を書き換える関数を記述したい場合、他関数で書き換えても良い変数のアドレスを、ほかの関数に教える必要があります。

たとえば、あなたの家にある家具を移動する仕事を、なんらかの業者に頼みたい場合、その業者に、あなたの家の住所を教える必要があります。

業者が、あなたの家の住所を知らないかぎり、あなたの家の家具は、どこにも移動しようがありません。


関数内の変数のアドレスも同じです。関数内の変数を、別関数をもちいて書き換え操作しようと思ったら、その別関数に変数の住所(= アドレス)を教える必要があります。

そのために、変数のアドレスを格納するポインタ型を用意するのです。

いわば、変数のアドレス番号についての情報は、まさに住所です。

ポインタとは、アドレスを格納するための型でありますが、さらに追加の仕様として、ポインタは「*ポインタ名」と記述される派生の型をもち、そのアドレスの対象になっている変数を書き換えることにも対応しています。つまり、「*ポインタ名」に数値を代入すると、なぜかその代入値にもとづき、そのポインタに格納されたアドレスに割り当てられた元々の変数の値が、書き変わります。

このようなポインタの仕様により、C言語では、ポインタをつかって、いま居る関数から、別の関数の変数を操作できます(ただし、その別の関数では、あらかじめアドレスとポインタを宣言しておく必要があります)。


なお、アドレスを知られてしまった変数は、他関数から書き換えをされる可能性がありますので、十分に気をつけて使用する必要があります。

このように、アドレスについての情報は、リスクが高いため、C言語では安全のため、&aなどのようにアドレスを宣言しないかぎり、原則的にはアドレスの情報を開示しないようになっているわけです。


くわしい説明[編集]

アドレスの格納[編集]

図1

ポインタには、 次のようにして別の変数へのメモリアドレスを格納できる。 このとき「ポインタ名は変数名を指す」という。

//例 ポインタに変数を指させる

int main(void)
{
	int i, *pi;//int型の変数iとポインタpiを宣言する。
	pi=&i;//piにiのメモリアドレスを格納する。※図1
}
書式
(ポインタによるアドレスの格納)
ポインタ名=&変数名;

ポインタの代入[編集]

「*ポインタ名」を使って、 そのメモリアドレスの指している先の変数の値を書きかえることもできる。

例えるなら、パソコンの、なんらかのファイルへのショートカットを使って目的のファイルへ移動して、そのファイルを書き換えるようなものである。あるいはインターネットで例えるなら、なんらかの投稿サイトへのリンクによって、その投稿サイトへ移動して、その投稿サイトに投稿するようなものである。

ポインタを使って変数を書き換える場合、書き換えられる変数は、そのポインタに代入されているアドレスに対応する変数が書き換えられるのである。

変数の書き換え命令の記法は、具体的には後述するように代入を使って、書き換えが行われる。

さて、 ポインタを使って、 ポインタが指す変数に値を間接的に代入するには、次のように*演算子を用いる。


//例 ポインタを用いて変数に代入する。
#include <stdio.h>

int main(void)
{
	int i, *pi; // int型の変数iとポインタpiを宣言する。
	pi=&i; // piにiのメモリアドレスを格納する。
	*pi=1234; // piが指すiに間接的に値を代入することにより、変数iの書き換えが行われる。
	printf("%d", i);
}
実行結果
 1234

上記のプログラム中では、一度も「i = 1234」とは記述されていないにもかかわらず、実行結果では、printfによる変数iの表示で「1234」の表示が行われている。このことから、ポインタ(上記コードではpi)を使用して「*pi = 1234」のように書くことで、ポインタの指し示す変数(上記コードではi)に代入をする事ができる事が分かる。


書式
(ポインタの示す変数への代入)
*ポインタ名=;

ポインタの参照[編集]

ポインタを使って、 ポインタが指す変数の値を間接的に参照するには、次のように*演算子を用いる。


//例 ポインタを用いて変数を参照する。
#include <stdio.h>

int main(void)
{
	int i, *pi;//int型の変数iとポインタpiを宣言する。
	i=1234;
	pi=&i;//piにiのメモリアドレスを格納する。
	printf("%d", *pi);//piの指すiの値を参照し表示する。
}
実行結果
 1234

一般に、「*ポインタ名」は「変数名」と同じものを表している。なので、上記のコードの場合、 printf("%d", *pi) とは、つまり、printf("%d", i) のことなので、よってprintfで「1234」を表示する。


書式
(ポインタの示す変数の参照)
*ポインタ名


ポインタとは[編集]

「ポインタ」という用語は、文脈によってポインタ型、ポインタ型の変数、ポインタ型の変数の値のいずれかを指す。 世間ではそれらをまとめて「ポインタ」と呼んでいるので、何を指すかは文脈で判断する。

ポインタ型はある型から派生し、 その型の実体を参照するための値(一般にメモリアドレス)を持つ。 つまり、ポインタ型は「ポインタ型」として単体であるのではなく、 あくまで他の型から派生して作られる。 ある型Tから派生したポインタ型を「Tへのポインタ」と呼ぶ。 つまり、intへのポインタや、doubleへのポインタなどがあるわけである。 [1]

ポインタ型の変数は、 別の変数へのメモリアドレスを格納し、 そのメモリアドレスの指している先の変数に、 間接的にアクセスできる。

ポインタ型の変数の値は、 前述したように一般にメモリアドレスである。 ただしメモリアドレスなら何でもいいわけではなく、 そのメモリアドレスは変数を指している必要がある。

さて、ポインタの宣言の記述は次のようになっている。

ポインタのデータ型 *ポインタ名;

ポインタのデータ型とポインタ名は、 変数の宣言のときと同様である。 同じデータ型のポインタは「,(コンマ)」で区切って一行で宣言できる。 その場合は、各々のポインタ名の前に*をつけること。

//例 ポインタの宣言

int main(void)
{
	int *pi;//int型のポインタpiを宣言する。
}

typedefをポインタに用いる[編集]

複数のポインタを一行で宣言するとき、しばしば下の例のような誤りを犯す場合がある。

//例 typedefを用いないポインタの宣言

int main(void)
{
	int* p1, p2;//int型のポインタであるp1と、int型の変数であるp2が宣言される。
}

上の例はtypedefを用いて、下の例のように記述することができる。

//例 typedefを用いたポインタの宣言

int main(void)
{
	typedef int *PINT;
	PINT p1, p2;//int型のポインタであるp1とp2が宣言される。
}

ポインタの応用[編集]

ポインタを指すポインタ[編集]

図2

別のポインタを指すポインタを作ることができる。

ポインタを指すポインタを宣言する時には、 ポインタ名の前に*単項演算子を1つ多くつける。

ポインタを指すポインタが指しているポインタが指している変数にアクセスするには、 ポインタを指すポインタ名の前に*単項演算子を1つ多くつける。

//例 ポインタを指すポインタの使用例
#include <stdio.h>

int main(void)
{
	int i, *pi, **ppi;//int型の変数i、ポインタpi、ポインタを指すポインタppiを宣言する。
	pi=&i;//piにiのメモリアドレスを代入する。
	ppi=&pi;//ppiにpiのメモリアドレスを代入する。※図2
	**ppi=1234;//ppiが間接的に指す変数iに値1234を代入する。
	printf("%d", i);
}

void型のポインタ[編集]

void型のポインタを宣言することができる。 void型のポインタは、他の型のポインタを格納できる。 間接演算子を使うときは型キャストして用いる。

// void型のポインタ
#include <stdio.h>

int main(void)
{
	char c=12;
	int i=34;
	float f=5.6;
	double d=7.8;

	char *cp=&c;
	int *ip=&i;
	float *fp=&f;
	double *dp=&d;
	void *vp;

	vp=cp;
	printf("vpが指すchar型の値は%i。\n", *(char*)vp);
	vp=ip;
	printf("vpが指すint型の値は%i。\n", *(int*)vp);
	vp=fp;
	printf("vpが指すfloat型の値は%f。\n", *(float*)vp);
	vp=dp;
	printf("vpが指すdouble型の値は%f。\n", *(double*)vp);
}

ポインタの演算[編集]

ポインタは、 4つの算術演算子(+, ++, -, --)を使って、 整数を加算減算することができる。 ポインタにn加えると、 ポインタのアドレスは「そのポインタの型のサイズ*n」だけ進む。

ポインタの演算は、ポインタが配列を指す場合によく使われる。

ところで、ポインタが指している変数には、 ポインタを使って、 普通の変数と同様に演算ができる。 その際、演算の優先順位には注意が必要である。

*p++;

上の記述では、pをインクリメントし、 そのメモリアドレスにある値を返す。

(*p)++;

上の記述では、pのメモリアドレスにある値をインクリメントする。

NULLポインタ[編集]

NULLポインタ(ナルポインタ、ヌルポインタ、空ポインタ)とは、 何も指しておらず、有効などんなポインタと比較しても等しくならないことが保証されているポインタのことである。 そのため、返却値にポインタをとる関数の異常時の返却値として用いたり、 連結リストで末尾のデータで次の要素がないことを示すために使われたりする。 なお、NULLポインタを経由して参照した場合、OSが異常を検知して即座にプログラムを停止させてくれることもあるが、 そうでない環境も存在するので注意が必要である。 [2]

NULLポインタの使用例として、 fopen関数はオープン操作に失敗した場合、空ポインタを返す。 詳細はC言語/標準ライブラリ/入出力#fopen関数を参照せよ。

ポインタの使用例[編集]

swap関数[編集]

C言語/関数#値渡しと参照渡しを参照せよ。

返却値以外の方法でデータを返す[編集]

C言語/関数#関数に複数の返却値を持たせるを参照せよ。

関数の引数として配列を渡す[編集]

C言語/関数#関数の引数として配列を渡すを参照せよ。

可変長配列[編集]

C言語/標準ライブラリ/一般ユーティリティ#malloc関数を参照せよ。

脚注[編集]

  1. ^ 『JISX3010:2003』p.24「6.2.5 型」
  2. ^ 前橋和弥著『C言語 ポインタ完全制覇』p.045 平成13年6月25日初版第4刷発行

参考文献[編集]

  • 日本工業標準調査会『JISX3010 プログラム言語C』2003年12月20日改正
  • 前橋和弥著『C言語 ポインタ完全制覇』平成13年6月25日初版第4刷発行