上記コード中の整数型変数aや文字列に対して、 例えば、以下の図のような形でメモリ上に配置される。int a = 314; char s[] = "abc";
C言語ではこのメモリ上に置かれたデータのアドレスをプログラムから扱える
ような機能を備えており、これがポインタと呼ぶものである。
そのために、データのアドレスを格納することができる変数を使用することが
出来、このアドレスを格納するための変数をポインタ変数と呼ぶ。
ポインタ変数は以下のように、変数名の直前に"*"をつけて宣言することで 使用可能となる。 それぞれ、変数 pa は整数(int)型のデータが格納されているアドレス を指すポインタ変数であり、変数 ps は文字(char)型のデータが格納さ れているアドレスを指すポインタ変数である。
int *pa; /* 整数(int)型ポインタ変数 */ char *ps; /* 文字(char)型ポインタ変数 */
以下のサンプルコードは
整数型と文字型のそれぞれのポインタ変数を使った例である。
※ サンプルコード中の"&"については次で説明するが、変数のアドレスを
取得する演算子である。
また、printf()関数の書式部での"%p"はポインタ変数を表示するための
書式指定である。
#include <stdio.h> int main(void) { int a = 314; char s[] = "abc"; int *pa; /* 整数(int)型ポインタ変数 */ char *ps; /* 文字(char)型ポインタ変数 */ pa = &a; /* 変数aのアドレスをポインタ変数paへ格納 */ ps = &s[1]; /* 文字列の2番目のアドレスをポインタ変数psへ格納 */ printf("pa=%p\n", pa); printf("ps=%p\n", ps); }
実行結果は以下のようになる。
ポインタ変数のそれぞれの値が表示される。
これらはそれぞれのデータがメモリ上に置かれているアドレスの値である。
pa=0x22f064 ps=0x22f061
以下のサンプルコードは ポインタ演算子を使った例である。
#include <stdio.h> int main(void) { int a = 314; int *pa; /* 整数(int)型のポインタ変数 pa */ pa = &a; /* アドレス演算子により変数aのアドレス計算 */ printf("before write (a=%d)\n", a); *pa = 141; /* 間接演算子によりポインタ経由で変数aの中身を書き換え */ printf("after write (a=%d)\n", a); }
実行結果は以下のようになる。 ポインタ変数を経由することで、変数の中身を変更されていることが分かる。
before write (a=314) after write (a=141)
このコードの場合の変数間の関係は以下の図のようになっている。
(アドレスの値は実行環境により異なるので注意)
変数aとポインタ変数paのどちらもメモリ上に存在し、
ポインタ変数paは変数aのアドレスを値として保持している。
最初、変数aは314という値であるが、ポインタ変数paを経由して
その値を141に書き換えられている。
最初の"itab[i]"は通常の配列の参照を行なう記述法である。
2つ目の"pi[i]"はポインタ変数を用いたもので、"pi"はポインタ変数であり、
配列変数ではないが、ポインタ変数を配列変数と同じように扱っている。
3つ目の"*(pi + i)"はポインタ変数にオフセットをつけて値を参照する方法で
あり、ポインタ変数が指すアドレスから"i"番目後方にあるデータの値を参照
する。
4つ目の"*(itab + i)"は、今度は逆に配列変数をポインタ変数的に扱っている
方法で、"itab"が配列の先頭のアドレスを指しており、その"i"番目後ろの要
素のアドレス、すなわち"i"番目の要素の値を参照する。
#include <stdio.h> int main(void) { int i; int itab[5] = {1, 2, 3, 5, 8}; int *pi; pi = &itab[0]; for (i = 0; i < 5; i++) printf("itab[%d]=%d pi[%d]=%d *(pi+%d)=%d *(itab+%d)=%d\n", i, itab[i], i, pi[i], i, *(pi+i), i, *(itab+i)); }
実行結果は以下のようになる。
どの方法を用いても同じ値が参照される。
itab[0]=1 pi[0]=1 *(pi+0)=1 *(itab+0)=1 itab[1]=2 pi[1]=2 *(pi+1)=2 *(itab+1)=2 itab[2]=3 pi[2]=3 *(pi+2)=3 *(itab+2)=3 itab[3]=5 pi[3]=5 *(pi+3)=5 *(itab+3)=5 itab[4]=8 pi[4]=8 *(pi+4)=8 *(itab+4)=8
以下の図は上記サンプルプログラムにおいての、ポインタと配列の関係を
示したものである。
ポインタ変数 pi に配列の先頭アドレス(つまり、&itab[0])を代入した後は、
ポインタ変数を配列のように使うことも出来れば、配列を使ってポインタのよ
うに使うことも出来る。
ここで、もしポインタ変数の値を1だけ大きい値に変更した場合、
ポインタ変数が指す配列の要素は配列の先頭ではなく、隣の要素を指すようになる。
つまり、配列中のある特定の要素を直接指し示すことができる。
(そのため、同じ配列要素を参照するためには、ポインタ変数に対するオフセッ
ト値(あるいは添字の数)をそれぞれ1つずつ小さくなる必要がある)
このようにポインタと配列は同じように使うことが可能である場合もあるが、 全く同じものではない。 例えば以下の例で、ポインタ変数は値の書き換えが出来るが、配列変数は書き 換えが出来ない。
int itab[5] = {1, 2, 3, 5, 8}; int *pi = &itab[0]; pi = pi + 1; /* OK (ポインタ変数は書き換えられる) */ itab = itab + 1; /* エラー (配列変数は書き換えできない) */
そのため、例えば、(ポピュラーな例であるが)変数の値を入れ替える関数
swap()を考えてみる。
以下のサンプルコードのように値を交換す
る関数swap()を定義しても、呼び出し側の変数u,vの値は書き換わらず、値が
交換されない。
#include <stdio.h> /* 2個の変数の値を入れ替える関数 (意図したとおりには動作しない例) */ void swap(int a, int b) { int tmp; tmp = a; a = b; b = tmp; } int main(void) { int u, v; u = 2; v = 3; printf("u=%d v=%d\n", u, v); swap(u, v); printf("u=%d v=%d\n", u, v); }
このコードの実行結果は以下のようになる。 関数swap()を呼び出しても値の交換がされていないことが分かる。
u=2 v=3 u=2 v=3
ポインタを使うとこの問題が解決できる。
関数の引数としてポインタ変数を渡す場合、ポインタ変数を介して値を書き換
えると、ポインタ変数が指していた変数の値も書き換わるため、
これを利用してC言語では参照渡し (call by reference)(関数の中で
変数の値を書き換えると呼び出し側の変数も書き換わる)を実現することが出来る。
先のコードを以下のサンプルコードのよう
に引数をポインタ変数で受け渡すようにすると、関数swap()を呼び出すと呼び
出し側の変数u,vの値が入れ替わる。
(引数がポインタ変数であるため、関数swap()を呼び出す際に変数のアドレス
を渡す必要がある)
#include <stdio.h> /* 2個の変数の値を入れ替える関数 (意図したとおりには動作する例) */ void swap(int *a, int *b) /* 引数がポインタ変数 */ { int tmp; tmp = *a; *a = *b; *b = tmp; } int main(void) { int u, v; u = 2; v = 3; printf("u=%d v=%d\n", u, v); swap(&u, &v); /* 変数のアドレスを渡す */ printf("u=%d v=%d\n", u, v); }
このコードの実行結果は以下のようになる。 関数swap()を呼び出すことで値の交換がされていることが分かる。
u=2 v=3 u=3 v=2
補足:
C言語の拡張言語であるC++言語では、ポインタを使うことなく、この参照渡し
が実現できる。
上記関数swap()の場合は以下のように、引数名の前に"&"をつけることで、
関数の中で行なった値の変更が呼び出し側の変数の値にも反映される。
ただし、これはC++言語の機能であるため、C言語では使えない。 (処理系によっては使えたりすることもあるが、コンパイラ依存のコードとな り移植性がなくなるため、本来は使うべきではない機能である)void swap(int &a, int &b) /* 参照渡し */ { int tmp; tmp = a; a = b; b = tmp; }
#include <ctype.h> #include <stdio.h> /* ↓以前のコード int string_length(char str[]) { int len = 0; while (str[len] != '\0') len++; return len; } */ int string_length(char *str) { int len = 0; while (*str++ != '\0') len++; return len; } /* ↓こういう書き方もできる int string_length(char *str) { int len = 0; while (*str++) len++; return len; } */ /* ↓以前のコード void string_toupper(char dst[], char src[]) { int i = 0; while (src[i] != '\0') { dst[i] = toupper(src[i]); i++; } dst[i] = '\0'; } */ void string_toupper(char *dst, char *src) { while (*src != '\0') *dst++ = toupper(*src++); *dst = '\0'; } /* ↓こういう書き方もできる void string_toupper(char *dst, char *src) { while (*dst++ = *src++) ; } */ void check_string(char str[]) { char tmpbuf[256]; printf("check '%s'\n", str); printf("length is %d\n", string_length(str)); string_toupper(tmpbuf, str); printf("%s\n", tmpbuf); printf("\n"); } int main(void) { check_string("A quick brown fox jumps over the lazy dog."); }
実行結果は同じである。
check 'A quick brown fox jumps over the lazy dog.' length is 42 A QUICK BROWN FOX JUMPS OVER THE LAZY DOG.