このページは、ポインタと配列の関係 と
配列を渡す方法(配列渡し) 文字列を渡す方法(文字列渡し)
を読んでおくと理解しやすくなります。
C言語では、原則的に、
戻り値として配列(文字列含む)を指定する事はできません。
他のプログラミング言語では普通にできた処理が
C言語では簡単にはできないのです。
ではどうすればいいか?
結論から言ってしまうと、配列をメモリ上に動的に確保し、
そのアドレスを返す事で「配列を返す」事ができます。
がしかし、その前に
どうして簡単にできないのか見ていきたいと思います。
(配列を返す方法だけ知りたい場合は、飛ばしてください。)
ひとつ例題を見てみましょう。
int型配列を返そうとするが・・・
int* makeIntArray(void) {
int array[] = {1, 2};
return array;
}
int型配列を戻り値として返してみたいと思います。
makeIntArray関数内で、int型配列を作り、
その配列を戻り値として指定してみました。
arrayはローカル変数ですので、
makeIntArray関数から出るとデータが失われてしまいます。
makeIntArray関数呼び出し側で、arrayの値を受け取る事ができますが、
arrayは &array[0] と同じ意味ですから、配列の先頭ポインタ値を表します。
arrayのポインタ値が指す先のデータは、
ローカル変数arrayのデータでしたので、
すでに無効になっているのです。
makeIntArray関数呼び出し直後であれば、
ローカル変数arrayのデータは正常に読み取る事ができるかもしれませんが、
何か適当な処理を続けていくうちに、
ローカル変数arrayのデータは書き換えられてしまうのです。
実際に例を見てみましょう。
#include <stdio.h>
int* makeIntArray(void);
int main(void) {
int* ret = NULL;
ret = makeIntArray();
printf("%d %d\n", ret[0], ret[1]);
printf("テスト\n");
printf("%d %d\n", ret[0], ret[1]);
}
int* makeIntArray(void) {
int array[] = {1, 2};
return array;
}
実行すると以下のような結果になります。
1 2
テスト
-858993460 11
このプログラム例では、
makeIntArray関数を呼び出して、int型配列arrayを受け取ります。
その直後、printfで配列のデータが正常に出力されていますが、
そのあとにprintfで適当な処理をしてから
再度、printfで配列のデータを出力してみると、
ワケの分からない数値が出力されてしまいました。
これは、ローカル変数arrayの値が書き換えられてしまった。
と言う証拠になるでしょう。
次に文字列を返す関数を作ってみましょう。
文字列を返そうとするが・・・
#include <stdio.h>
char* retStr(void);
int main(void) {
char* str = NULL;
str = retStr();
printf(str);
}
char* retStr(void) {
char cp[] = "C言語";
printf(cp);
printf("\n");
return cp;
}
結果は以下のようになりました。
C言語
フフフフフフフフフフフフ
何やら怪しげな結果になってますね(笑)
内容について見ていきましょう。
retStr関数では、char型配列を作り、
戻り値として返しています。(返そうとしています。)
配列を作った直後にprintfで表示してみると、
正常に表示されました。
これは特に問題はないと思います。
次にretStr関数呼び出し元では、
ローカル変数cpのアドレス値を受け取る事になります。
ローカル変数ですから、retStr関数呼び出しが終わった時点で
無効になっています。
printfで表示しようとしても、
意味不明な値が出力されているのが分かると思います。
上記のint配列を返した例では、
まだ配列の値が変更されずに保持されていましたが、
今回は既に書き換えられてしまったようです。
(環境により書き換えられるタイミングは異なると思います。)
文字列を直接返す方法
今度は文字列を直接返してみましょう。
char* retStr(void);
int main(void) {
char* str = NULL;
str = retStr();
printf(str);
}
char* retStr(void) {
return "aabbcc";
}
aabbcc
retStr関数では、””で囲んだ文字列(文字列リテラル)を返しています。
呼び出し元でも無事に受け取って、
printfで正常に表示できているように見えます。
これは内部的にどのような処理をしているか説明します。
“aabbcc”と記述すると、
どこかの領域に”aabbcc”が確保されて、
その先頭ポインタが返ってくるのです。
確保された領域は静的な特徴を持っていて、
関数から抜けても値が保持されています。
つまり、静的変数と同じです。
静的変数ですから、もし、関数呼び出し元で、
str[0] = ‘k’; のように文字列の中身を書き換えようとすると、
何らかのエラーか例外が発生し、異常終了となるでしょう。
しかし、注意して扱えば、文字列リテラルを戻り値にする事は可能です。
ただ、推奨されない書き方である事は確かでしょう。
このように、単純に配列を戻り値として返す事はできないのです。
どうすればいい?
ではどうすれば、実現できるのでしょうか?
関数内で配列を宣言すると、ローカル変数になってしまい、
関数から抜けるとデータが失われてしまいます。
そこで、動的メモリを使います。
動的に配列をメモリ上に確保すれば、ローカル変数ではなくなるので、
関数から抜けてもデータが保持されています。
そのデータを返してやれば良い。と言う事になります。
戻り値を使ってint型配列を返す方法(malloc)
まずは、実際にプログラムを見てみましょう。
#include <stdio.h>
#include <stdlib.h>
int* makeintarray(void);
int main(void) {
int* array=NULL;
array = makeintarray();
int i = 0;
while (i < 3) {
printf("array[%d] = %d\n", i, array[i]);
i++;
}
free(array);
}
int* makeintarray(void) {
int* ip = NULL;
ip = (int*)malloc(sizeof(int) * 3);
if (ip == NULL) {
printf("配列作成失敗\n");
}
ip[0] = 10;
ip[1] = 50;
ip[2] = 100;
return ip;
}
出力は以下のようになります。
array[0] = 10
array[1] = 50
array[2] = 100
これは、int型配列をmakeintarray関数で作成してもらい、
それを表示するプログラムです。
makeintarray関数は、新しくint型配列を作り、
データを設定し、配列を返してくれる関数です。
malloc関数は、動的にメモリを確保してくれる関数です。
詳しくはこちらのページで解説しています。
そして、戻り値として配列の先頭アドレスを返しています。
return &ip[0]; と書く事もできます。
このプログラムでは、
配列の要素数が分かっているので問題は起きませんが、
通常は、新しい配列を作ったら、
配列の先頭アドレスと要素数の2つの情報が必要となります。
引数にアドレスを渡すなど、工夫して情報を受け取るようにします。
関数で複数の値を返す方法はこちらのページを参考にして下さい。
このプログラム例では、戻り値に配列の先頭アドレスを指定した形です。
これで、int型配列を返す事ができるようになりました。
また、返された配列は動的にメモリ確保されているので、
自分で管理し、使用しなくなったら、free関数で解放させる事が必要です。
このあたりもこちらのページで解説しています。
引数を使ってint型配列を返す方法(malloc)
今度は、戻り値ではなく、引数を使って配列を返してもらう方法です。
#include <stdio.h>
#include <stdlib.h>
void makeintarray(int** r);
int main(void) {
int* array=NULL;
makeintarray(&array);
int i = 0;
while (i < 3) {
printf("array[%d] = %d\n", i, array[i]);
i++;
}
free(array);
}
void makeintarray(int** r) {
int* ip = NULL;
ip = (int*)malloc(sizeof(int) * 3);
if (ip == NULL) {
printf("配列作成失敗\n");
}
ip[0] = 10;
ip[1] = 50;
ip[2] = 100;
*r = ip;
}
array[0] = 10
array[1] = 50
array[2] = 100
結果は変わりません。
今度は、makeintarray関数の戻り値がvoidになり、
引数に新しく作成した配列の先頭アドレスを
格納してもらうような感じになります。
書き方がダブルポインタになっていて、
分かりづらい感じになってしまいますが、
これで実現できます。
ダブルポインタの簡単な説明はコチラ
今回のダブルポインタの概念を図で示してみました。
これなら分かりやすいでしょう。
戻り値を使って文字列を返す方法(malloc)
文字列を返す事もできます。やり方はint型配列と同じです。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char* makeString(void);
int main(void) {
char* str=NULL;
str = makeString();
printf("str = %s\n", str);
free(str);
}
char* makeString(void) {
char* cp = NULL;
cp = (char*)malloc(sizeof(char) * 4);
if (cp == NULL) {
printf("配列作成失敗\n");
}
strcpy_s(cp, 4, "ABC");
return cp;
}
出力は以下のようになります。
str = ABC
これは、char型配列をmakeString関数で作成してもらい、
それを表示するプログラムです。
makeString関数は、新しくchar型配列を作り、
データを設定し、配列を返してくれる関数です。
数値型配列と違って、文字列を返しているので、
終端文字があります。そのため、要素数は必要ありません。
これで、char型配列を返す事ができるようになりました。
malloc関数の注意事項に関してはint型配列の時と同じです。
引数を使って文字列を返す方法(malloc)
戻り値ではなく、引数を使って文字列を返す方法です。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void makeString(char** r);
int main(void) {
char* str=NULL;
makeString(&str);
printf("str = %s\n", str);
free(str);
}
void makeString(char** r) {
char* cp = NULL;
cp = (char*)malloc(sizeof(char) * 4);
if (cp == NULL) {
printf("配列作成失敗\n");
}
strcpy_s(cp, 4, "ABC");
*r = cp;
}
今度は、makeString関数の戻り値がvoidになり、
引数に新しく作成した配列の先頭アドレスを
格納してもらうような感じになります。
書き方がダブルポインタになっていて、
分かりづらい感じになってしまいますが、
これで実現できます。
ダブルポインタの簡単な説明はコチラ
今回のダブルポインタの概念を図で示してみました。
これなら分かりやすいでしょう。
配列を返す方法をいくつかご紹介しましたが、
クセがありますので、注意して使って下さい。