メモリアドレスとは
C言語で重要な概念の一つに「アドレス」があります。メモリ(ここでは物理メモリではなく、仮想的な論理メモリ空間を指します)は、1バイトごとに一意な番号が割り当てられています。たとえば、定規の目盛りをイメージしてください。0から999までの目盛りが刻まれた定規で、35mmや256mmの位置を正確に指し示せます。メモリも同様に線形に配置されており、32ビットOSでは0から4,294,967,295までの範囲が存在します。この番号をアドレスと呼び、通常は16進数(例: 0x12ff7c)で表記します。
C言語では、定義された変数ごとにメモリ上の領域(int型は4バイト、char型は1バイトなど)が確保され、それぞれのバイトにアドレスが付与されます。アドレス演算子 & を用いることで、変数のアドレスを取得できます。以下のコード例をご覧ください。
#include <stdio.h>
int main()
{
int idx;
int vals[10] = {1,2,3,4,5,6,7,8,9,0};
char chars[10] = {'c','l','a','n','g','u','a','g','e','\0'};
for (idx = 0; idx < 10; idx++)
{
printf("int Addr: 0x%x, Value: %d\n", &vals[idx], vals[idx]);
}
printf("\n");
for (idx = 0; idx < 10; idx++)
{
printf("char Addr: 0x%x, Value: %c\n", &chars[idx], chars[idx]);
}
return 0;
}
ポインタの基本
アドレスが「定数」であるのに対し、ポインタは「変数」としてアドレスを格納します。ポインタ変数は、メモリ上の「遊標」のように、指し示す場所を変更できます。ポインタ変数の宣言は以下の形式で行います。
型指定子 *変数名;
ここで * はポインタであることを示し、型指定子 はポインタが指す先のデータ型を表します。また、間接参照演算子 * を用いて、ポインタが指すアドレスに格納された値を取得できます。
#include <stdio.h>
int main()
{
int value = 2014;
int *ptr = &value;
printf("value Addr = 0x%x, value = %d\n", &value, value);
printf("ptr = 0x%x, *ptr = %d\n", ptr, *ptr);
printf("間接参照: %d\n", *&value);
return 0;
}
配列とポインタ
配列の要素には添字でアクセスできるほか、ポインタを使ってアクセスすることも可能です。配列名は配列の先頭アドレス(0番要素のアドレス)を表す定数です。以下はその例です。
int data[10]; /* 10個の整数配列 */
int *ptr; /* 整数型ポインタ */
ptr = &data[0]; /* 先頭要素のアドレスを代入 */
C言語では、data は &data[0] と等価です。よって、次のように書けます。
ptr = data; /* ptr = &data[0] と同じ */
int *ptr2 = data; /* 宣言と同時に初期化 */
ポインタ ptr が先頭要素を指しているとき、ptr + i は data[i] のアドレス、*(ptr + i) はその値を表します。ポインタ演算の結果、ptr + 1 は次の要素(data[1])を指します。
配列要素へのアクセス方法は2通りあります。
- 添字法:
data[i]の形式 - ポインタ法:
*(data + i)または*(ptr + i)の形式
#include <stdio.h>
int main()
{
int i;
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr;
for (i = 0; i < 10; i++)
{
printf("p: %d , arr: %d\n", *(p++), *(arr + i));
}
printf("\n");
return 0;
}
注意:ポインタ変数は ++ や -- で自身の値を変更できますが、配列名(arr)は定数なので値を変更できません。
文字列とポインタ
文字列は文字配列または文字ポインタで扱えます。文字ポインタを使って文字列定数を指すことができます。
char *str = "example.com/blog";
これはポインタ str に文字列定数の先頭アドレスを格納します。一方、文字配列を使う方法もあります。
char buffer[] = "example.com/blog";
ここで buffer は配列名で、文字列の先頭アドレスを表します。
両者には重要な違いがあります。文字ポインタ str は変数なので別の文字列を指すように変更できますが、指し先の文字列定数の内容を変更することはできません。一方、文字配列 buffer は要素の内容を変更できます(例: buffer[0] = 'E';)。この点を理解しておくことが重要です。