【C言語】関数から複数の値を返却する方法

複数の関数を返却する方法の解説ページアイキャッチ

このページでは、C言語において「関数から複数の値を返却する方法」について解説していきます。

まず断っておくと、C言語では関数から return できる値は1つのみです。したがって、関数から複数の値を返却したい場合、return 以外の手段で値を返却する必要があります。その手段とは、具体的には「アドレスの利用」になります。

関数から複数の値を返却する方法

C言語では関数から return できる値は1つだけではあるものの、関数の引数には複数の値を指定することが可能です。

引数に複数の値を指定することができることを利用し、引数に返却してもらいたい値を「格納してもらうためのメモリのアドレス」を指定するようにすることで、複数の値を返却する関数を実現することができます。

関数から複数の値を返却する場合の処理の流れ

まずは、関数から複数の値を返却する際の処理の流れについて解説していきます。

説明しやすくするため、ここからは具体的な関数例を用いて解説していきたいと思います。今回は、指定された2つの整数に対する「除算結果」と「剰余算結果」を返却する関数 func を用いて解説していきます。

作成する関数が返却する値の説明図

ただし、除算や剰余算は除数が 0 になると 0 除算が発生して実行することができません。そのため、0 除算が発生するなどして関数が正常に処理できない場合は、そのことを関数呼び出し側に伝えるために false を返却するようにしたいと思います。逆に関数が正常に処理できた場合は true を返却します。

つまり、この関数 func では下記の3つの値を返却する必要があります。

  • 関数処理結果(true or false
  • 除算結果(整数)
  • 剰余算結果(整数)

このような関数 func は、関数呼び出し側と関数 func 側とで次のような処理の流れを組むことによって実現することができます。

返却値を保存してもらうためのメモリを用意する

まず、関数呼び出し側は除算結果と剰余演算結果を関数 func に保存してもらうための「メモリの確保」を行います。このメモリの確保は変数宣言を行うことで実現することができます。

関数呼び出し側が事前にメモリを用意しておく様子

関数処理結果用のメモリを確保していないのは、1つの値に関しては関数の return により返却することが可能だからです。

つまり、関数 func に保存してもらうためのメモリとして事前に確保しておく必要があるのは、関数から返却してもらいたい値の数 - 1 個分となります(関数の返却値の型を void 型にする場合は 関数から返却してもらいたい値の数 分のメモリが必要になります)。

用意したメモリのアドレスを引数で渡す

続いて、関数呼び出し側から関数 func を実行します。この時、関数 func の引数には、関数が処理を行うための引数(今回の場合は除算と剰余算を行うために必要になる値、すなわち被除数と除数の値)だけでなく、「事前に確保して用意したメモリのアドレス」も含めて引数指定を行います。

関数に引数として値の保存先となるメモリのアドレスを渡す様子

このメモリを変数宣言により確保した場合は、その宣言した変数の変数名の前に & 演算子を付加することで、その変数(メモリ)のアドレスを取得することが可能です。

このアドレスを引数で関数 func に渡すことで、関数 func はどのアドレスに結果を保存すれば良いかが分かるようになります。

引数で受け取ったアドレスに値を保存する

関数 func が実行されたので、次は関数 func の処理が行われることになります。もし 0 除算などが発生して処理できない場合は関数処理結果として falsereturn して関数を終了します。

処理が行える場合は、引数で渡された被除数と除数の値から除算結果と剰余算結果を計算します。そして、その2つの結果を「引数として受け取ったアドレスのメモリに保存」します。

関数が引数で受け取ったアドレスに返却したい値を保存する様子

アドレスからメモリに値を保存する際には、間接演算子 * を利用して代入を実行します。

関数を終了する(return して値を返却する)

メモリに結果を保存した後は、関数 func は関数処理結果として truereturn して関数を終了します。

returnで値を返却するとともに関数を終了する

関数が終了すれば関数呼び出し側に処理が戻ることになります。この時、関数呼び出し側は関数 func の返却値として、まず関数処理結果(true or false)を受け取ることになります。

用意したメモリに保存された値を取得する

さらに、事前に用意した2つのメモリ(変数)には関数 func によって保存された除算結果と剰余算結果が保存されていることになります。

すなわち、関数呼び出し側は関数 func から下記の3つの値を受け取ったことになります。

  • 関数処理結果(true or false
  • 除算結果(整数)
  • 剰余算結果(整数)

後は、関数呼び出し側は受け取った値を取得し、その値を利用して処理を継続していけば良いことになります。

関数呼び出し側がアドレスを介して関数から値を受け取る様子

こんな感じで、アドレスを利用することで関数から実質的に複数の値を返却することができるようになります。

ですので、要は、複数の値を返却する関数を実現するためには、上記のような流れが実現できるように関数側の処理や関数呼び出し側の処理を実装してやれば良いことになります。

特に関数側の処理を実装する上でポイントになるのは下記の2つになります。

  • 引数でアドレスを受け取る
  • 受け取ったアドレスに返却したい値を保存する

あとは、関数呼び出し側でも下記に関しては注意が必要です。

  • 返却値を受け取るためのメモリを用意する

ここからは、上記の3つに焦点を当てて解説していきたいと思います。

スポンサーリンク

引数でアドレスを受け取る

複数の値を返却する関数を実現するためには、まずは関数の引数でアドレスを受け取れるようにする必要があります。

C言語では、アドレスは基本的にポインタに格納しますので、関数の引数に、元々関数が処理を実行するのに必要な引数に加え、ポインタ引数も用意するようにします。

先程の例のように、return する値に加えて除算と剰余算の結果を返却したい場合は、2つのポインタ引数を用意する必要があります。

例えば下記のように引数を用意すれば、ret_div のアドレスのメモリに除算結果を、ret_mod のアドレスのメモリに剰余算結果を保存することができるようになります。そしてこれにより、除算結果と剰余算結果を関数呼び出し側に渡すことができるようになります(もちろん関数呼び出し側から引数を適切に設定してもらう必要があります)。

ポインタ引数の用意
bool func(int dividend, int devisor, int *ret_div, int *ret_mod) {
    /* 略 */
}

dividenddevisor はそれぞれ割り算における被除数と除数(割り算は 被除数 / 除数 で行われる)を指定するためのもので、関数 func が処理を行うために必要な値となります。

return 以外で返却したい値が2つあるので、これらの引数に加えて2つの結果返却用の引数 ret_divret_mod を用意するようにしています。もし、もっと多くの値を返却したいのであれば、返却したい値の個数に応じてポインタ引数を追加してやれば良いです。 

ちなみにですが、上記では関数 func の返却値の型を bool にしていますが、この bool 型を利用するためには stdbool.h をインクルードしておく必要があります。

この辺りの解説は下記ページで行っていますので、bool 型や truefalse を利用したいような場合は是非読んでみてください。

C言語でbool型を扱う方法の解説ページアイキャッチC言語でのbool型の使い方

受け取ったアドレスに返却したい値を保存する

続いて、引数で受け取ったアドレスのメモリに返却したい値を保存するようにしていきます。

ここでポイントになるのが、アドレスからメモリに値を保存する処理になります。変数からメモリに値を直接保存するのではなく、アドレスからメモリに値を保存する場合は、「間接演算子 *」を利用する必要があります。

ポインタ引数にはアドレスが格納されているわけですから、ポインタ引数からメモリに値の保存を行う場合にも、この間接演算子 * を利用する必要があります。

例えば、引数でアドレスを受け取る で用意したポインタ引数 ret_divret_mod に格納されたアドレスに引数で受け取った被除数と除数に対する除算結果・剰余算結果を保存するためには、下記のように間接演算子 * を利用した上で値の代入を行う必要があります(falsereturn する場合の処理を省略しています。省略していない関数に関しては 複数の値を返却する関数の例 で紹介しています)。

指定されたアドレスへの結果の保存
bool func(int dividend, int devisor, int *ret_div, int *ret_mod) {
    *ret_div = dividend / divisor;
    *ret_mod = dividend % divisor;

    return true;
}

正、下記のように間接演算子を利用せずに代入を行なった場合、ret_divret_mod の値が上書きされるだけであり、元々 ret_divret_mod に格納されていたアドレスのメモリへの値の保存は行われません。

指定されたアドレスへの結果の保存
bool func(int dividend, int devisor, int *ret_div, int *ret_mod) {
    ret_div = dividend / divisor;
    ret_mod = dividend % divisor;

    return true;
}

何が起きているかを簡単に ret_div の動作に注目して説明しておくと、まず関数実行時には、関数呼び出し側が事前に用意したメモリのアドレスがポインタ引数 ret_div に格納された状態になります。つまり、ret_div は関数呼び出し側が用意したメモリを指している状態になります。

関数実行直後のret_divの様子

それに対し、間接演算子を利用せずに直接 ret_div に除算結果を代入した場合、ret_div に「格納されているアドレス」が、その除算結果に上書きされます。つまり、ret_div は除算結果の値をアドレスと考え、そのアドレスのメモリを指すことになります。例えば除算結果が3なのであれば、3番地のアドレスを指すことになります。

ret_divの指す先が変わってしまう様子

ポインタの指す先が変わるだけで、元々用意されていたメモリには何の値も保存されません。ですので、これだと関数呼び出し側に除算結果を渡すことができません。

その一方で、間接演算子を利用した場合、ret_div そのものの値が上書きされるのではなく、ret_div の「指すメモリ」に値が保存されることになります。

ret_divの指す先のメモリが変わる様子

ですので、*ret_div に除算結果を代入してやれば、ret_div の指すメモリに除算結果を保存することができ、関数呼び出し側にも除算結果を渡すことができるようになります。

ポインタの場合、通常の変数と異なり代入の仕方が2通りあり(間接演算子の利用の有無の2通り)、間接演算子を利用しなかった場合はポインタの指す先(ポインタに格納されているアドレス)が変化し、間接演算子を利用した場合はポインタの指すメモリ(ポインタに格納されているアドレスのメモリ)が変化することになります。

この違いでプログラムの動作が大きく異なるので注意してください。

ちょっと解説が長くなりましたが、要はポインタ引数で結果を格納すべきメモリのアドレスが分かっているので、あとはポインタ引数で受け取ったアドレスに対して間接演算子 * を利用してメモリへの結果の保存を行えば、関数呼び出し側に対してメモリを介して結果を渡すことができます。

返却値を受け取るためのメモリを用意する

複数の値を返却可能な関数の実現方法についての解説は以上になります。

次に、関数呼び出し側で行う結果保存用のメモリの用意について解説しておきます。

返却値を保存してもらうためのメモリを用意する で解説したように、C言語においては変数宣言自体がメモリの用意であると考えて良いです(変数宣言を行うことで変数が適切な位置のメモリに自動的に配置されるため、メモリが用意されたと考えられる)。

ですので、前述の func 関数を実行するのであれば、下記のように2つの変数の変数宣言を行い、それらの変数のアドレスを関数の引数に指定してやれば良いです。変数のアドレスは、アドレス演算子 & により取得することができます。

変数宣言によるメモリの用意
int val_div, val_mod;

func(10, 3, &val_div, &val_mod);

func 関数が上手く作られていれば、関数実行後は val_divval_modfunc 関数が返却した値が格納された状態になります。

ただし、関数の引数がポインタであるからといって、事前に変数宣言する変数をポインタ変数にすると上手く動作しなくなる可能性があるので注意してください。

例えば下記のように変数宣言を行なって func 関数を実行した場合、func 関数は意図した通りに動作してくれないはずです。

変数宣言によるメモリの用意
int *p_val_div, *p_val_mod;

func(10, 3, p_val_div, p_val_mod);

上記の2つのパターンでどのように動作が異なるのかについて解説しておきます(説明を簡単にするため val_div のみに注目して解説していきますが、val_mod に関しても同様のことが言えます)。

前者の場合、func 関数の引数には「変数 val_div のアドレス」が指定されることになります。val_div は当然変数宣言されていますので、val_div 用のメモリが用意された状態で、そのメモリのアドレスが func 関数に引数 ret_div として渡されることになります。

func 関数実行直後の引数 ret_div の様子を表したのが下の図となります。引数 ret_div には val_div のアドレスが渡されますので、ret_divval_div のメモリを指していることになります。

前者のパターンの関数実行直後のret_divの様子

ですので、間接演算子を利用して ret_div からメモリに値を保存すれば、関数呼び出し側で用意したメモリである val_div に値が格納されることになります。

その一方で後者の場合、func 関数の引数 ret_divには変数 p_val_div に格納されている値が渡されることになります。p_val_div はポインタ変数ですので、格納されている値もアドレスとして扱われますが、p_val_div には初期値を格納していませんので func 関数に渡されるアドレスは不定アドレスとなります(どこ指しているか分からないアドレス)。

そのため、func 関数実行直後の ret_div の様子を表すと下の図のような感じになります。要は、p_val_div 自体のアドレスではなく、p_val_div に格納されているアドレスが ret_div にも格納されることになります。

後者のパターンの関数実行直後のret_divの様子

p_val_div は事前に用意したメモリのアドレスが格納されているわけではありませんので、そのアドレスのメモリに ret_div から除算結果を保存しようとすると、メモリアクセス違反が発生してプログラムが強制終了させられることになります(事前に用意したメモリにアクセスするとメモリアクセス違反が発生する)。

引数で不定アドレスを渡していることも問題ではあるのですが、ここでの1番の問題は「事前に用意したメモリ」のアドレスを func 関数に渡せていないところです(用意したメモリは “確保したメモリ”・”予約したメモリ” と言った方が分かりやすいかもしれません)。

前者のパターンでは変数宣言により事前に用意したメモリのアドレスを func 関数に渡せていますが、後者のパターンでは事前に用意したメモリのアドレスが func 関数に渡せていません(しかも不定アクセス)。

関数側で関数呼び出し側に渡すための値を保存してもらうためには、事前に関数呼び出し側で用意したメモリのアドレスを関数に渡す必要があります。

逆に変数宣言をポインタ変数として行なったとしても、そのポインタ変数に事前に用意したメモリのアドレスを格納しておけば、上手く動作させることができます。

例えば、下記のように別途変数宣言を行なった変数のアドレスを事前に格納しておけば、変数宣言により用意されたメモリのアドレスを func 関数に渡せるので、正常に func 関数を動作させることができます。

別途変数宣言を行なった変数のアドレスを渡す
int *p_val_div, *p_val_mod;
int val_div, val_mod;

p_val_div = &val_div;
p_val_mod = &val_mod;

func(10, 3, p_val_div, p_val_mod);

上記の場合、func 関数実行後に val_divval_modfunc 関数が返却した値が格納されることになります(*p_val_div*p_val_mod から取得するのでも良い)。

また、下記のように malloc 関数で確保したメモリのアドレスを func 関数に渡すのでも良いです。

malloc関数で確保したメモリのアドレスを渡す
int *p_val_div, *p_val_mod;

p_val_div = malloc(sizeof(int));
p_val_mod = malloc(sizeof(int));

func(10, 3, p_val_div, p_val_mod);

free(p_val_div);
free(p_val_mod);

上記の場合、func 関数実行後に *p_val_div*p_val_modfunc 関数が返却した値が格納されることになります。

ただし、malloc 関数で確保したメモリは使い終わったら free する必要があるので注意してください。

この malloc関数については下記ページで解説していますので、詳しくは下記ページを参考にしていただければと思います。

malloc解説ページのアイキャッチ【C言語】malloc関数(メモリの動的確保)について分かりやすく解説

スポンサーリンク

複数の値を返却する関数の例

ここまでの解説にも利用してきた、引数で被除数と除数を受け取って関数処理結果(true or false)・除算結果(整数)・剰余算結果(整数)の3つの値を返却する関数 func を利用するプログラムの全体のソースコードを紹介しておきます。

複数の値を返却する関数の例
#include <stdio.h>
#include <stdbool.h>

bool func(int dividend, int divisor, int *ret_div, int *ret_mod) {

    if (divisor == 0) {
        return false;
    }

    if (ret_div == NULL || ret_mod == NULL) {
        return false;
    }

    *ret_div = dividend / divisor;
    *ret_mod = dividend % divisor;

    return true;
}

int main(void) {
    int val_div, val_mod;
    int x, y;

    printf("dividend:");
    scanf("%d", &x);
    printf("divisor:");
    scanf("%d", &y);

    if (func(x, y, &val_div, &val_mod)) {
        printf("div = %d, mod = %d\n", val_div, val_mod);
    } else {
        printf("divisor is zero...\n");
    }

    return 0;
}

わざわざ引数にアドレスを指定する理由

さて、ここまで複数の値を返却する関数を実現するためには、引数としてアドレスを受け取れるようにする必要があることを前提にして解説してきました。

なぜ、わざわざアドレスを受け取れるようにする必要があるのでしょうか?

例えば、複数の値を返却する関数の例 で紹介したソースコードを下記のように変更し、関数 func でアドレスを受け取らないようにした場合でも上手く動作してくれそうではないでしょうか?

引数でアドレスを受け取らない場合
#include <stdio.h>
#include <stdbool.h>

bool func(int dividend, int divisor, int ret_div, int ret_mod) {

    if (divisor == 0) {
        return false;
    }

    ret_div = dividend / divisor;
    ret_mod = dividend % divisor;

    return true;
}

int main(void) {
    int val_div, val_mod;
    int x, y;

    printf("dividend:");
    scanf("%d", &x);
    printf("divisor:");
    scanf("%d", &y);

    if (func(x, y, val_div, val_mod)) {
        printf("div = %d, mod = %d\n", val_div, val_mod);
    } else {
        printf("divisor is zero...\n");
    }

    return 0;
}

結論としては、上記の func 関数では1つの値しか返却することはできません。

C言語においては、関数呼び出し時の引数の渡し方は「値渡し」となっています。

値渡しとは、関数呼び出し時に指定した変数の値のみがコピーされて関数の引数に渡される方式の「引数の渡し方」です(他プログラミング言語には。引数の渡し方が「参照渡し」となっているものもあります)。

つまり、関数には関数呼び出し時に指定した引数の値が渡されますが、関数呼び出し時に指定した変数と関数の引数とは全く別物です。ですので、関数の引数を変更したとしても、関数呼び出し時に指定した変数には全く影響を及ぼしません。

引数にアドレスを含ませないと複数の値が返却できないことを示す図

そのため、上記ソースコードのように関数の中で引数の値を変更したとしても、関数呼び出し側に値を返却することにはなりません。

ですが、コピーされるのが値であったとしても、関数呼び出し側で用意した変数等のメモリのアドレス値を関数に渡した場合、関数側は関数呼び出し側の変数等のメモリのアドレスを知ることができます。

そして、そのアドレスを知っていれば、関数側から関数呼び出し側の変数等のメモリにアクセスすることができます(関数側で間接演算子を利用すれば)。

引数にアドレスを含ませることで複数の値が返却できるようになることを示す図

そのため、関数呼び出し側で使用しているメモリや変数を結果的に変更するようなことができます。例えば、上の図において関数 func 内で *c = 50 を実行すれば、関数呼び出し元で使用している変数 z の値が 50 に変化します。

さらに、これを利用することで複数の値を関数呼び出し側に返却することが可能になります。ですので、複数の値を返却する関数を実現するためには、引数でアドレスを受け取れるようにしてやる必要があります。

まとめ

このページでは、C言語における「関数から複数の値を返却する方法」について解説しました!

C言語 において関数から return できる値は1つのみですが、関数呼び出し元で事前にメモリを用意して引数でそのメモリのアドレスを渡すようにすることで、複数の値を関数から返却できるようになります。

関数から返却できる値が1つのみだと不便なことは結構多いですし、複数の値を返却したいような場合は今回紹介した流れや関数の作り方を参考にしていただければと思います。

特に関数を作る際には引数にアドレスを持たせるようにすることと間接演算子を利用するあたりがポイントになるかと思います。アドレスやポインタの勉強にもなると思いますので、是非「複数の値を返却する関数」の作成に挑戦してみてください!

オススメの参考書

C言語学習中だけど分からないことが多くて挫折しそう...という方には、下記の「スッキリわかるC言語入門」がオススメです!

まず学習を進める上で、参考書は2冊持っておくことをオススメします。この理由は下記の2つです。

  • 参考書によって、解説の仕方は異なる
  • 読み手によって、理解しやすい解説の仕方は異なる

ある人の説明聞いても理解できなかったけど、他の人からちょっと違った観点での説明を聞いて「あー、そういうことね!」って簡単に理解できた経験をお持ちの方も多いのではないでしょうか?

それと同じで、1冊の参考書を読んで理解できない事も、他の参考書とは異なる内容の解説を読むことで理解できる可能性があります。

なので、参考書は2冊持っておいた方が学習時に挫折しにくいというのが私の考えです。

特に上記の「スッキリわかるC言語入門」は、他の参考書とは違った切り口での解説が豊富で、他の参考書で理解できなかった内容に対して違った観点での解説を読むことができ、オススメです。題名の通り「なぜそうなるのか?」がスッキリ理解できるような解説内容にもなっており、C言語入門書としてもかなり分かりやすい参考書だと思います。

もちろんネット等でも色んな観点からの解説を読むことが出来ますので、分からない点は別の人・別の参考書の解説を読んで解決していきましょう!もちろん私のサイトも参考にしていただけると嬉しいです!

入門用のオススメ参考書は下記ページでも紹介していますので、こちらも是非参考にしていただければと思います。

https://daeudaeu.com/c_reference_book/

同じカテゴリのページ一覧を表示