プログラム中で使用しているデータはコンピュータの中のどこかのメモリに 格納されている。例えば、プログラム中に、
int a;
char c;
a = 5;
c = 'a';
のような部分があったら、
| アドレス(番地) | データ(2進数) |
|---|---|
| 999 | -- |
| 1000 | 00000101 |
| 1001 | 00000000 |
| 1002 | 00000000 |
| 1003 | 00000000 |
| 1004 | 01100001 | 1005 | -- |
のようになっている箇所があるはずである (int型は4byte、char型は1byte使う)。
アドレス(番地)とは、コンピュータが持っているメモリに 順番に振られている番号のことで、「データをどこにしまっておいたか」を この番号で記憶している。C言語では、この番号を直接扱うことが出来る。
int *p; double *q;のように、「型名 * 変数名」で宣言する。この場合、 int *は「int型を指すためのポインタ変数」、 double *は「double型を指すためのポインタ変数」 という意味である。
また、(通常の)変数に対して、「&演算子」を使うことで その変数の格納されているアドレスを取り出すことが出来る。
int x; int *p; p = &x;とすると、通常の変数xの格納されているアドレスを ポインタ変数pに代入することになる。
また、ポインタ変数からそのポインタの指している部分の「中身」を 取り出すには、「*演算子」を使う。
int x;
int *p;
p = &x; /* p に変数xの存在するアドレスを代入 */
x = 1;
printf("%d\n", *p);
*p = 2; /* pの示す場所に2を代入 */
printf("%d\n", x);
printf("%d\n", *p);
上の例で分かるように、「*演算子」は代入文の左辺にも
使えることに注意。
int x;
int *p;
p = &x;
printf("%p\n", p);
のようにprintfの中で「%p」を使えば、番地そのものの値を
表示させることも出来る。
#include <stdio.h>
int main(void)
{
printf("%lu\n", sizeof(int));
printf("%lu\n", sizeof(double));
printf("%lu\n", sizeof(float));
printf("%lu\n", sizeof(char));
printf("%lu\n", sizeof(int *));
printf("%lu\n", sizeof(double *));
return 0;
}
|
ある計算機でこれを実行したところ、
4 8 4 1 8 8となった。「ポインタ変数の大きさ」は、番地を格納するので、指し示す 先のデータの型によらず、その計算機のメモリ空間の大きさで決まる。 32bitの計算機なら32bit=4byte、64bitの計算機なら64bit=8byteが普通である。
#include <stdio.h>
int main(void)
{
char c;
int x;
int *p;
char *q;
p = &x;
printf("%p\n", p);
q = &c;
printf("%p\n", q);
p++;
printf("%p\n", p);
q++;
printf("%p\n", q);
p += 2;
printf("%p\n", p);
return 0;
}
|
これをある計算機で実行したところ、
0x7ffff135c708 0x7ffff135c70f 0x7ffff135c70c 0x7ffff135c710 0x7ffff135c714となった。16進数で読みづらいが、説明の通りに動いていることを確認して欲しい。
int a[10];
のような配列があったとき、「a」は 、その配列の先頭を表す
「定数ポインタ」であるとみなすことが出来る。例えば、
#include <stdio.h>
int main(void)
{
int a[10];
int *p;
p = &(a[0]);
printf("%p\n", p);
p = &(a[1]);
printf("%p\n", p);
p = &(a[9]);
printf("%p\n", p);
p = a;
printf("%p\n", p);
return 0;
}
|
これをある計算機で実行したところ、
0x7fffb77d4280 0x7fffb77d4284 0x7fffb77d42a4 0x7fffb77d4280となった。このように、「a」は、「a[0]のアドレス」に 等しく、「a[i]のアドレス」はaに i*4を足したもの になっている(4はsizeof(int))。また、
#include <stdio.h>
int main(void)
{
int i;
int a[10];
int *p;
for (i=0; i<10; i++) {
a[i] = i*i;
}
p = a;
for (i=0; i<10; i++) {
printf("%d ", *p);
p++;
}
printf("\n");
return 0;
}
|
のように書くと、配列の要素を順に表示することが出来る。
実は、 「a[i]」 という通常の配列へのアクセスは、
「*(a + i)」と全く等価である。
malloc、freeによるメモリの確保、開放
次のプログラムは、自然数nを入力し、次にn個数値を入力してもらって、
そのn個の数値を逆順に出力するものである。
#include <stdio.h>
int main(void)
{
int n;
int i;
scanf("%d", &n);
int a[n];
for (i=0; i<n; i++) {
scanf("%d", &(a[i]));
}
for (i=n-1; i>=0; i--) {
printf("%d\n", a[i]);
}
return 0;
}
|
しかし、このプログラムはコンパイルできずエラーになってしまう (注意)。
このようなプログラムを書くには、mallocとfreeを用いれば良い。 これらを使うには、
#include <stdlib.h>が必要である。mallocは、OSから使用可能なメモリを 割り当ててもらう関数で、byte単位で大きさを指定する。 だから、例えばint10個分のメモリが欲しい場合は、
a = (int *)malloc(40);
あるいは、
a = (int *)malloc(sizeof(int) * 10);
のようにする。後者の方がスマートだろう。「(int *)」は、
ポインタのキャスト(型変換)である。mallocはそれが何を
指し示す用途に使われるか分からないので、「void *」という
特殊な型のポインタを返す。それで、intを指すポインタint *
に変換してやる必要がある。freeは、割り当てられたメモリ領域が不要になったとき、 OSにそれを返却する関数である。これをやらないと、プログラム終了時まで 返却されない。
先のプログラムは、malloc、free使って書き直すと、
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int n;
int i;
int *a;
scanf("%d", &n);
a = (int *)malloc(sizeof(int) * n);
for (i=0; i<n; i++) {
scanf("%d", &(a[i]));
}
for (i=n-1; i>=0; i--) {
printf("%d\n", a[i]);
}
free(a);
return 0;
}
|
のようになる。
なお、搭載するメモリ量の制限などにより、mallocは常に成功するわけでは無い。 失敗すると NULLポインタ(後述) が返ってくる。よって、
a = (int *)malloc(sizeof(int) * n);
if (a == NULL) {
printf("no memory\n");
return 1;
}
のようにちゃんとエラーチェックするのが行儀がよい。
これに対して、関数の中で書き換えると呼び出し元の値も書き変わるような 受渡し方を「参照渡し」(call by reference)と呼ぶ。C言語では、ポインタを使うと 参照渡しが実現出来る。
#include <stdio.h>
void swap_int(int *x, int *y)
{
int tmp;
tmp = *x;
*x = *y;
*y = tmp;
}
int main(void)
{
int a, b;
a = 2;
b = 3;
swap_int(&a, &b);
printf("%d %d\n", a, b);
return 0;
}
|
scanfの中で引数の値の頭に「&」を 付けていたのは、実は参照渡しである。scanfは変数の値を 「書き換えてもらう」関数のため、そのようにする必要がある。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
printf("%p\n", NULL);
return 0;
}
|
どのポインタとも一致しないポインタである。使うには、
#include <stdlib.h>が必要。ポインタ変数が「今現在どこも指していない」ことを表すのに 使われる。多くのコンパイラの実装では、「0番地」を指している。 (多くのOSにおいて0番地はシステムが使用していてユーザに開放されない 領域であるため、ユーザ使用領域と一致することは無いため。)