★ポインタ(ダブルポインタ1)★


今回は、他の関数で領域を確保する方法について説明します。

まず、「他の関数で領域を確保する」とはどのようなイメージか書きます。

例えば、main関数が次のようにあるとします。

<sample program 170-01>

#include <stdio.h>
#include <stdlib.h>

#define DATA 5

int main(void)
{
    int *pData;

    int i;

    Allocate(); /* 引数、戻り値は不明 */

    for (i = 0; i < DATA; i++) {
        pData[i] = i + 1;
    }

    for (i = 0; i < DATA; i++) {
        printf("%d\n", pData[i]);
    }    

    free(pData);

    pData = NULL;

    return 0;
}

Allocate関数はポインタ変数pDataに領域を確保する関数だと思ってください。

このAllocate関数を作りたいのですが、何を引数として渡せば良いでしょうか。

何となくpDataを渡せば良いように感じていませんか?

適当に考えるのでは無く、しっかりと考えてみましょう。


ポインタ変数pDataが宣言された時の状態は↓です。

        +----------+
  pData |  不  定  |
        +----------+

通常の自動変数と同じく中身は「不定」です。

pDataを渡す、つまり

Allocate(pData);

と書くという事は、この「不定」な値を渡すということです。

「不定」な値を渡されたAllocate関数は何をすれば良いのでしょうか?

正直「不定」な値をもらっても何も出来ません。

やりたいことは、ポインタ変数pDataの中に「確保した領域の先頭アドレス」を入れる事です。

                                     確保した領域
        +----------+                 +----------+
  pData | 00E63260 | ← 00E63260番地 |          |
        +----------+                 +----------+
                                     |          |
                                     +----------+
                                     |          |
                                     +----------+
                                     |          |
                                     +----------+
                                     |          |
                                     +----------+

では、どうすれば良いかというと、ポインタ変数pData自体のアドレスを渡すという方法があります。

例えば、pDataのアドレスが「0105FBE8」だったとします。

        0105FBE8番地
        +----------+
  pData |  不  定  |
        +----------+

この「0105FBE8」番地をAllocate関数に渡す事が出来れば、間接的にpDataの中身にアクセス出来ます。

プログラムで書くと、

Allocate(&pData);

のように、ポインタ変数のアドレスをAllocate関数に渡すのです。

この時に気を付けなければならないのが、受け取る関数の引数の書き方です。

ポインタを受け取るときには、

void Func(int *p);

のように書けば良かったのですが、「ポインタ変数のアドレス」を受け取る時には、

void Func(int **pp);

のように「*」を2つ付ける必要があります。

※ポインタ変数名の「pp」は適当に付けた名前です。


試しに、まず次のプログラムを作ってみましょう。

<sample program 170-02>

#include <stdio.h>

int main(void)
{
    int data = 15;

    int *pPointer;

    printf("data = %d\n", data);

    printf("&data = %p\n\n", &data);

    pPointer = &data;

    printf("*pPointer = %d\n", *pPointer);

    printf("pPointer = %p\n", pPointer);

    printf("&pPointer = %p\n", &pPointer);

    return 0;
}

<実行結果>

data = 15
&data = 0074F9FC

*pPointer = 15
pPointer = 0074F9FC
&pPointer = 0074F9F0
続行するには何かキーを押してください・・・

※実行結果(アドレス部分)は毎回変わる可能性があります。


分かりやすくするために図にしてみます。

           0074F9FC番地
           +----------+
  data     |       15 |
           +----------+

           0074F9F0番地
           +----------+
  pPointer | 0074F9FC |
           +----------+

番地が近いので紛らわしいですが、このようになってます。

この状態で、

*pPointer

と書くと、pPointerが指している(0074F9FC)番地の中身、「15」を指すことになります。


では、ポインタ変数のアドレスを受け取る関数を追加してみます。

<sample program 170-03>

#include <stdio.h>

void DoublePointer(int **ppPointer);

int main(void)
{
    int data = 15;

    int *pPointer;

    printf("data = %d\n", data);

    printf("&data = %p\n\n", &data);

    pPointer = &data;

    printf("*pPointer = %d\n", *pPointer);

    printf("pPointer = %p\n", pPointer);

    printf("&pPointer = %p\n\n", &pPointer);

    DoublePointer(&pPointer);

    return 0;
}

void DoublePointer(int **ppPointer)
{
    printf("**ppPointer = %d\n", **ppPointer);

    printf("*ppPointer = %p\n", *ppPointer);

    printf("ppPointer = %p\n", ppPointer);
}

<実行結果>

data = 15
&data = 006BFED8

*pPointer = 15
pPointer = 006BFED8
&pPointer = 006BFECC

**ppPointer = 15
*ppPointer = 006BFED8
ppPointer = 006BFECC
続行するには何かキーを押してください・・・

※実行結果(アドレス部分)は毎回変わる可能性があります。


こちらも分かりやすくするために図にしてみます。

            006BFED8番地
            +----------+
  data      |       15 |
            +----------+

            006BFECC番地
            +----------+
  pPointer  | 006BFED8 |
            +----------+
            
            +----------+
  ppPointer | 006BFECC |
            +----------+

ppPointer の中には pPointer のアドレスが入っています。

*ppPointer と書くことで、ppPointerが指している番地の中身(pPointerの中身)を指す事が出来ます。


さて、本題に戻りましょう。

領域を確保するために作った、Allocate関数の引数に pDataのアドレスを渡せるように作ってみます。

<sample program 170-04>

#include <stdio.h>
#include <stdlib.h>

#define DATA 5

void Allocate(int **ppData);

int main(void)
{
    int *pData;

    int i;

    Allocate(&pData);

    for (i = 0; i < DATA; i++) {
        pData[i] = i + 1;
    }

    for (i = 0; i < DATA; i++) {
        printf("%d\n", pData[i]);
    }    

    free(pData);

    pData = NULL;

    return 0;
}

void Allocate(int **ppData)
{

}

この状態を図にして、どこに領域を確保すれば良いか考えましょう。

pDataのアドレスは「0105FBE8」番地だったとします。

        0105FBE8番地
        +----------+
  pData |  不  定  |
        +----------+

この中に確保した領域の先頭アドレスを入れたいです。

pDataのアドレスを渡された Allocate関数の方を図にします。

         +----------+
  ppData | 0105FBE8 |
         +----------+

ppDataはポインタのアドレスを受け取っています。

*ppData

と書くと、ppDataが指している番地の中身を指しますから、

  0105FBE8番地の中身 → pDataの中身

ということになり、今のところは「不定」になっているデータを指します。

ここに確保した領域の先頭アドレスを入れれば良いのですから、次のようなプログラムになるはずです。

<sample program 170-05>

#include <stdio.h>
#include <stdlib.h>

#define DATA 5

void Allocate(int **ppData);

int main(void)
{
    int *pData;

    int i;

    Allocate(&pData);

    for (i = 0; i < DATA; i++) {
        pData[i] = i + 1;
    }

    for (i = 0; i < DATA; i++) {
        printf("%d\n", pData[i]);
    }    

    free(pData);

    pData = NULL;

    return 0;
}

void Allocate(int **ppData)
{
    *ppData = (int*)malloc(sizeof(int) * DATA);
}

<実行結果>

1
2
3
4
5
続行するには何かキーを押してください・・・

ただ、これでは確保できなかった時のエラー処理が出来ませんので、次のように変えましょう。

<sample program 170-06>

#include <stdio.h>
#include <stdlib.h>

#define DATA 5

int Allocate(int **ppData);

int main(void)
{
    int *pData;

    int i;

    if (!Allocate(&pData)) {
        return 1;
    }

    for (i = 0; i < DATA; i++) {
        pData[i] = i + 1;
    }

    for (i = 0; i < DATA; i++) {
        printf("%d\n", pData[i]);
    }    

    free(pData);

    pData = NULL;

    return 0;
}

int Allocate(int **ppData)
{
    *ppData = (int*)malloc(sizeof(int) * DATA);

    if (!ppData) {
        return 0;
    }

    return 1;
}

実行結果は変わりませんが、エラーになった時に異常終了するようになりました。

Allocate関数に戻り値を加え、領域の確保に成功したら1を失敗したら0を返すようにしました。

こうしておけば、

if (!Allocate(&pData)) {
    return 1;
}

のように、失敗した時に異常終了(1を返す)ようにプログラムを組むことが出来ます。


次へ

戻る

目次へ