C言語 配列を返す方法 文字列を返す方法 return

このページは、ポインタと配列の関係
配列を渡す方法(配列渡し) 文字列を渡す方法(文字列渡し)
を読んでおくと理解しやすくなります。


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になり、
引数に新しく作成した配列の先頭アドレスを
格納してもらうような感じになります。

書き方がダブルポインタになっていて、
分かりづらい感じになってしまいますが、
これで実現できます。

ダブルポインタの簡単な説明はコチラ

今回のダブルポインタの概念を図で示してみました。
これなら分かりやすいでしょう。

配列を返す方法をいくつかご紹介しましたが、
クセがありますので、注意して使って下さい。

タイトルとURLをコピーしました