今回は、他の関数で領域を確保する方法について説明します。
まず、「他の関数で領域を確保する」とはどのようなイメージか書きます。
例えば、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を返す)ようにプログラムを組むことが出来ます。