「ポインタの基礎」

ポインタ

「ポインタ」とは"pointer"と書き「pointする物(者)」 すなわち「指し示す物(者)」という意味である。 では、C言語での「ポインタ」が何を指し示すのかと言うと、 データが格納されている場所である。 C言語に限らずプログラムにおいて、変数に代入されたデータはそれが整数型 であろうが、文字列であろうが、全て記憶領域(メモリ)上に格納されており、 それぞれに専用の場所が割り当てられている。 このデータが格納されているメモリ上の場所をアドレスと呼ぶ。


    int a = 314;
    char s[] = "abc";

上記コード中の整数型変数aや文字列に対して、 例えば、以下の図のような形でメモリ上に配置される。
(実行環境などにより実際のアドレスの値は異なるので注意) メモリ上のデータの図

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に書き換えられている。 ポインタ変数と変数のメモリ上での関係

配列とポインタ

C言語の配列とポインタは似ている面がある。
例えば、以下のサンプルコードでは 同じ配列の要素を参照するために4種類の方法を使っている。

最初の"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だけ大きい値に変更した場合、 ポインタ変数が指す配列の要素は配列の先頭ではなく、隣の要素を指すようになる。 つまり、配列中のある特定の要素を直接指し示すことができる。
(そのため、同じ配列要素を参照するためには、ポインタ変数に対するオフセッ ト値(あるいは添字の数)をそれぞれ1つずつ小さくなる必要がある)

ポインタと配列の関係その2

このようにポインタと配列は同じように使うことが可能である場合もあるが、 全く同じものではない。 例えば以下の例で、ポインタ変数は値の書き換えが出来るが、配列変数は書き 換えが出来ない。

int itab[5] = {1, 2, 3, 5, 8};
int *pi = &itab[0];

pi = pi + 1;           /* OK (ポインタ変数は書き換えられる) */
itab = itab + 1;       /* エラー (配列変数は書き換えできない) */

関数とポインタ

C言語において関数呼び出しの際の引数の受け渡しは値渡し (call by value)であり、変数の値をコピーしてから関数を呼び出すため、 関数の中で変数の値を書き換えても呼び出し側の変数は影響を受けない(書き 換わらない)。

そのため、例えば、(ポピュラーな例であるが)変数の値を入れ替える関数 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()の場合は以下のように、引数名の前に"&"をつけることで、 関数の中で行なった値の変更が呼び出し側の変数の値にも反映される。

void
swap(int &a, int &b)    /* 参照渡し */
{
    int tmp;
    tmp = a;
    a = b;
    b = tmp;
}
ただし、これはC++言語の機能であるため、C言語では使えない。 (処理系によっては使えたりすることもあるが、コンパイラ依存のコードとな り移植性がなくなるため、本来は使うべきではない機能である)

文字列とポインタ

ポインタを用いることで、以前紹介した文字列のサンプルコードをもっとC言語らしく記述す ることができる

#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.


文責:大津