なぜポインタが必要なのか?

これまでのサンプルから、変数のアドレスを保存して、そこから間接的にアクセスできることが確認できました。では、このような機能がなぜ必要なのでしょうか。ポインタには、多様な応用方法が考えられますが、代表的な例として、変数の位置を異なる関数の間で伝達する手段が考えられます。

ローカル変数が見える範囲は、変数が宣言された複合文の中だけでした。よって、異なる関数のローカル変数にアクセスすることはできず、関数の間でデータを共有するにはグローバル変数を使うか、または関数のパラメータや戻り値を使って通信を行うかのいずれかです。

グローバル変数は共有に便利ですが、ある程度プログラムに慣れてくると、グローバル変数の危険性を理解できるようになります。グローバル変数を多用しすぎると、どの変数が、どの関数から変更されるのか、把握が難しくなります。予期せぬ場所から変数の値を書き換えられ、データの整合性がなくなるといった問題が発生する可能性が高まってしまいます。能力のある技術者であれば、グローバル変数は必要最小限の範囲でしか使いません。

グローバル変数を使わずに関数でデータを受け渡しするには、パラメータと戻り値を使う方法が考えられます。ところが、関数の呼び出し時にパラメータに値を渡すという処理は、代入と同じで値のコピーが行われます。単一の整数であれば簡単ですが、いくつかのデータを組み合わせた構造的な情報を受け渡しするには適しません。パラメータの数が多すぎるのも良くありませんし、結果として返せる戻り値は 1 つしかありません。構造体や配列を使えば、複数のデータを 1 つの変数として受け渡しできると考えるかもしれませんが、大きなデータを複製するのは負荷がかかります。

こうした問題を解決するために、複雑なデータの受け渡しをポインタで行います。関数のパラメータでポインタを受け取ることで、関数の処理で必要な値を複製することなく受け取ることができ、関数の処理結果をポインタから設定できます。配列や構造体などの大型のデータを関数の間で取引するときにポインタの知識は必須となるでしょう。

Sample09

#include <stdio.h>

struct Point
{
    int x, y;
};

void PrintPoint(struct Point *pt)
{
    printf("X=%d, Y=%d\n", pt->x, pt->y);
}

void AddPoint(struct Point *pt1, struct Point *pt2)
{
    pt1->x += pt2->x;
    pt1->y += pt2->y;
}

int main(void)
{
    struct Point pt1 = { 10, 20 };
    struct Point pt2 = { 100, 200 };

    PrintPoint(&pt1);
    AddPoint(&pt1, &pt2);

    PrintPoint(&pt1);
    AddPoint(&pt1, &pt2);

    return 0;
}

実行結果

サンプル09の実行結果

Sample09 は、構造体へのポインタを関数のパラメータで受けとり、ポインタと通して呼び出し元のローカル変数である構造体を操作する例です。座標を表す Point 構造体と、Point 構造体の値を表示する PrintPoint() 関数と、構造体の値を加算する AddPoint() 関数を用意しています。

関数のパラメータで Point 構造体へのポインタを受け取っています。ここが純粋な値の場合、構造体全体が複製されてデータが渡されますが、ポインタであればアドレスを受け渡しするだけなので軽量です。加えて、ポインタを通じて呼び出し元の変数の値を操作できます。AddPoint() 関数では、pt1 パラメータに指定された構造体のメンバに、pt2 パラメータに渡された構造体のメンバの値を加えています。PrintPoint() 関数に構造体へのポインタを渡して値を出力すると、正しく値の加算が行われていることが確認できます。

このように、ポインタを使うことによって他の関数からでもローカル変数の値を更新できます。よって、複雑な構造体や配列の初期化や処理結果を受け取る方法として応用できます。構造体や配列のような大きなデータを受け渡しする関数では、基本的にポインタが使われています。