コンピュータは内部的には電気信号の on/off で物事を記憶している。 この記憶の最小単位の事を 1bit(1ビット)と呼ぶ。
コンピュータの内部には、そのような記憶する場所が膨大にあり、CPU はその記憶領域(メモリ)を用いて作業を行っている。 すべての記憶領域は、アドレス(address)といわれる番号が割り振られ、管理されている。 いままで用いてきた変数は、ある一定のアドレス範囲に対して名前を割り振り、人間が理解しやすいようにしたものである。 この番地は 1bit ごとではなく、8bit をまとめた 1byte(1バイト)ごとにつけられている。 1バイトで表される情報量は 2×2×2×2×2×2×2×2 = 28 = 256 通りである。 より多くの種類の情報を取り扱いたい場合は、複数バイトをまとめて取り扱う。 たとえば2バイトを用いれば 256×256 = 65536通り、 4バイトを用いれば 256×256×256×256 = 4294967296 通りの情報を保持することができる。
コンピュータが行っているプログラムは、特定のアドレスで保持されている内容を参照したり、指定された内容を特定のアドレスに保存している事になる。
しかしながら、人間がアドレスで考えるのはたいへん(「52f4番地から4バイトの領域に、12345 という値を代入せよ」とか。)なので、
その領域について名前をつけることにしたものが変数である。
したがって、変数は、その型(サイズ)によって、特定のアドレス範囲を確保することになる。
例えば、int 型の変数 i
を宣言すると、次のように特定の範囲(赤い範囲)が
i
という名前で参照できることになる。
こうすると、「52f4番地から4バイトの領域に、12345 という値を代入せよ」の代わりに、 「int 型変数 iに 12345 を代入する」という表現ですむことになる。
scanf と getchar や printf と putchar の関係に少し似ている。
C では、この変数のアドレスを直接扱うことができる。
変数のアドレスを調べるためには、演算子&
を、printf の書式指定子としては %p
を用いる。
また、演算子 sizeof
を変数に対して用いると、その変数の占める領域の大きさをバイト単位で求めることができる。
プログラムの実行時に、例えば上のようなメモリ配置になっていた瞬間に
#include <stdio.h> int main() { int i; char a[6]; i = 1; printf("&i = %p sizeof(i) = %d\n", &i, sizeof(i)); printf("&a = %p sizeof(a) = %d\n", &a, sizeof(a)); printf("&a[3] = %p sizeof(a[3]) = %d\n", &a[3], sizeof(a[3])); }
というプログラムを実行すると
~% ./a.out &i = 0x52f4 sizeof(i) = 4 &a = 0x52fa sizeof(a) = 6 &a[3] = 0x52fd sizeof(a[3]) = 1 ~%
という出力を得る。
プログラミング E で使用しているコンピュータや通常のパソコンでは、 アドレスは32ビット(16進数で8桁)で表示される。次のプログラムを実行して、変数のアドレス、大きさと保持している値の関係を確認してみよ。
/* address.c */ /* 変数のアドレスや占める領域の大きさを調べる */ #include <stdio.h> int main() { int i; char a[6]; for(i = 0; i < 6; ++i) { /* 変数 i のアドレスと、i の中身を表示 */ printf("&i = %p, sizeof(i) = %d, i = %d\n", &i, sizeof(i), i); } printf("\n"); /* 区切りのための空白行 */ for(i = 0; i < 6; ++i) { /* 配列変数 a の要素のアドレスを表示 */ printf("&a[%d] = %p ", i, &a[i]); /* 配列変数 a の要素の大きさを表示 */ printf("sizeof(&a[%d]) = %d\n", i, sizeof(a[i])); } }
問 9-1: address.c
を元に、int 型の配列の要素のアドレスがどうなっているかを調べるプログラムを作成し、自分の理解と一致しているかを確認せよ。
問 9-2: もし、配列の添字として宣言した要素数より大きな添字をつけると何が起こるかを考えてみよ。
ポインター(pointer)とは英和辞典を索くと「指す人(もの)」という意味を持つことがわかる。 C 言語で出てくるポインターとは「アドレスを入れる事ができる変数」の事である。 ポインタ型の変数を宣言する為には、
の用に「*
」をつけて宣言を行う。
宣言の際に前に書かれている型は、その内容であるアドレスに存在する変数の型、
言い換えると、そのポインタ変数でどの型の変数のアドレスを扱うのかを示している。
ポインタ変数の利用の仕方を順にあげる。
ポインタ変数に代入されているものはアドレス値そのものなので、通常変数のアドレスを代入することができる。 また、ポインタ変数同士だと、通常の変数と同様に利用することで、アドレスをやり取りすることができる。
int i; int *ptr, *a; ptr = &i; a = ptr;
とすると、ポインタ変数 ptr
, a
両方が、変数 i
のアドレスを持つことになる。
ポインタ変数に代入されているものは、アドレスそのものであるが、そのアドレスの内容を表示することもできる。
指し示しているアドレスの内容を参照するには、*
演算子をつける。
たとえば、ポインタ変数 ptr
の示している領域の内容を参照するには *ptr
とする。
次のプログラムを実行すると、j
には 10 が代入される。
int i, j; int *ptr; i = 10; /* 変数 i に 10 を代入 */ ptr = &i; /* ptr に i のアドレスを代入 */ j = *ptr; /* 変数 j に ptr で指し示されている領域 * (つまり、i)の内容を代入 */
ここまでを、たとえ話にしてみるとこんな感じ。
問 9-3: 次のプログラムを実行するとどのような出力を得られるか?
/* prob9-3.c */ #include <stdio.h> int main() { char a[6] = "Hi"; char *p; p = &a[0]; /* a[0] のアドレスを p に代入 */ while (*p) { printf("%c\n", *p++); } }
課題8 | 日程表 | 課題10 |