C言語/ポインタ

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

ポインタの基本[編集]

アドレス[編集]

オブジェクトはメモリー上に領域が確保され、値が格納されます[1]

メモリーをバイト単位の一次元配列と考えた場合、その配列の添え字がアドレスです。

バイト単位の一次元配列であることを強調するため、バイトアドレスとも呼ばれます。

1バイトは一般的に8ビットで、10進数で256(28)通りの値を格納できます。256通り以上の値を格納するに連続した複数のバイトを一塊として使用します。

オブジェクトのアドレスを取得するには、オブジェクト名の前に&演算子(アドレス演算子)を付けます。

&オブジェクト名
オブジェクトaのアドレス
&a
変数のアドレスを表示する
#include <stdio.h>

int main(void) {
  int a = 1234;
  printf("変数aの値は%d\n", a);
  printf("変数aのアドレスは%p\n", &a);
  printf("main関数のアドレスは%p\n", &main);
  return 0;
}
実行結果
変数aの値は1234
変数aのアドレスは0x7ffe13618a64
main関数のアドレスは0x401130

この様に、オブジェクトには変数の他、関数も含まれます(アドレスの値は実行条件によって異なります)。

&main

は、main関数のアドレスを得る式で、単に

main

でも同じ意味です。この様に関数もメモリー上のオブジェクトなのでアドレスを持ちます。


ポインタ変数の宣言[編集]

ポインタの機能として、ポインタ変数を宣言する事によって、なんらかの別の変数のアドレスを格納する事ができるだけの領域を確保します。 ポインタ変数がその変数の値として格納するものは、指し示すべき対象のオブジェクトが格納されたアドレスです。 ポインタ変数の宣言では、指し示すべき対象のデータの型を指定します。

このようなメモリー上のデータの記憶位置を指定するデータである「アドレス」を格納するための変数のことをポインタ変数という。

上記コード例のようにC言語では、変数宣言において、変数名の直前に「*」を伴って宣言された変数は、(その*よりも前に書かれた型のデータを指し示す)ポインタ変数の宣言になります。

ポインタ変数の名前は、他の変数の名前と同様に有効な識別子でる必要があります。

ポインタ変数の初期化とNULL[編集]

初期化を伴わないポインタ変数 pの宣言
    int *p;

ポインタpの指し示すオブジェクトの型は int であることは確定していますが、ポインタ変数pの値は不定で、このまま p を使って参照するととても危険です

そこでポインタ変数の宣言と同時に初期値を与えることが強く推奨されます。

初期値として適切な式が即座に思いつかない時にはNULLで初期化することが(ワーストケースを避けるため)妥当です。

NULLで初期化するポインタ変数 pの宣言
    int *p = NULL;

とすれば、p を NULL で初期化出来ます。 NULLは、どの様な型へのポインタとして有効な値で、なおかつ有効なアドレスではないことが言語仕様で保証されています。

このことで、NULLを使った参照を実行時に補足できる可能性が出てきます(実行時にNULLによる参照を補足できるかは言語処理系と実行環境依存です。一般に、UnixではSIGSEGVが、Windowsでは構造化例外処理による例外が上がりますが、0番地からどの程度離れた番地への参照を補足するかなどでバリエーションがあります。)。

    int *junk234;

では、ポインタ変数の名前は junk234 です。

コード例
#include <stdio.h>

int main(void) {
    int i = 1234,
       *p = &i;
    printf("整数変数iのアドレスは%p\n", &i);
    printf("ポインタ変数pの値は%p\n", p);
}
実行結果
整数変数iのアドレスは0x7ffcb0de444c 
ポインタ変数pの値は0x7ffcb0de444c

このように、ポインタ変数pの値として、整数変数iのアドレスが格納できている事が分かる。

ポインタとアドレスの目的

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

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

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

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

たとえば、ある関数Aにある2つの変数xとyを交換するための別関数B(いわゆる「swap関数」)では、原理的に、関数Bは関数Aを書き換える必要があります。(具体例は『C言語/関数』で説明。)


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

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

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


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

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

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

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

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


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

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

くわしい説明[編集]

アドレスの格納[編集]

ポインタに変数を指させる
int main(void)
{
	int i, *pi = &i;//piにiのアドレスを格納する。※図1
	return 0;
}

ポインタへアドレスを格納

書式
ポインタ = &変数名;

ポインタを介した代入[編集]

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

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

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

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


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

int main(void) {
	int i, *pi = &i;
	*pi = 1234;
	printf("%d\n", i);
}
実行結果
1234

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

ポインタを介した代入

書式
*ポインタ = ;

ポインタの参照[編集]

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

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

int main(void){
	int i = 1234,
      *pi = &i;
	printf("%d\n", *pi);
}
実行結果
1234

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


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

ポインタとは[編集]

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

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

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

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

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

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

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

//例 ポインタの宣言

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

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

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

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

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

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

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

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

ポインタの応用[編集]

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

図2

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

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

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

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

int main(void) {
  int i,
     *pi = &i,
    **ppi = &pi;

    **ppi = 1234;
  printf("%d", i);
}

ppiが間接的に指す変数iに値1234を代入する。

void型へのポインタ[編集]

void型へのポインタを宣言することができます。 void型へのポインタは、他の型へのポインタを格納できます。 間接演算子を使うときは型キャストして用いる。 void型へのポインタはインクリメントやデクリメントと整数との加減算は出来ない。

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

int main(void) {
	char c = 12, *cp = &c;
	int i = 34, *ip = &i;
	float f = 5.6, *fp = &f;
	double d = 7.8, *dp = &d;
	void *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);
    return 0;
}

ポインタの演算[編集]

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

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

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

*p++;

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

(*p)++;

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

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

swap関数[編集]

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

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

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

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

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

動的長配列[編集]

脚註[編集]

  1. ^ register 型修飾子を伴って宣言された変数は例外です。
  2. ^ 『JISX3010:2003』p.24「6.2.5 型」

参考文献[編集]

  • 日本工業標準調査会『JISX3010 プログラム言語C』2003年12月20日改正