C言語のきほん|関数から構造体を返す方法

関数から構造体を返せるようになると、複数の結果をひとまとまりで気持ちよく扱える。

C言語では、関数の引数として構造体を渡せるだけでなく、関数の戻り値として構造体を返すこともできます。これはとても便利な仕組みです。なぜなら、関連する複数の値を1つずつバラバラに返すのではなく、ひとまとまりのデータとして返せるからです。

たとえば、計算結果として座標とサイズをまとめて返したいとき、あるいは名前と点数のように意味のつながった値を一緒に返したいとき、構造体を戻り値にするとコードがすっきりします。関数の役割もはっきりして、読みやすいプログラムになりやすいです。

はじめて見ると、関数の中で作った構造体を返して本当に大丈夫なのかなと少し不安になるかもしれません。でも、C言語では構造体を戻り値として返すとき、構造体全体がコピーされて呼び出し元へ渡されるので、安全に利用できます。

ここでは、関数から構造体を返す基本的な考え方から、実際のサンプルプログラム、データの流れ、注意点まで、順番にやさしく確認していきましょう。

関数から構造体を返すとはどういうことか

普通の関数では、戻り値として int や double を返すことがよくありますよね。
たとえば、次のような関数です。

int add(int a, int b)
{
    return a + b;
}

この関数は計算結果として 1 個の整数を返しています。

これに対して、構造体を返す関数では、複数のメンバを持った1つのデータのまとまりを返します。
つまり、戻り値として「複数の情報をまとめて返す」ことができるわけです。

これは、次のような場面で特に役立ちます。

使いどころ返したい内容の例
計算結果をまとめたいとき横幅、高さ、面積
変換結果を返したいとき年月日を別形式に変換した結果
関連する値を一度に返したいとき商品名と税込価格
初期化済みデータを返したいとき設定済みの構造体

構造体を戻り値として使えるようになると、関数の表現力がかなり広がります。

サンプルプログラム

テストの点数情報から評価結果を表す構造体を作って返すプログラムを作成します。
この例では、国語・数学・英語の点数を受け取り、合計点と平均点を持つ別の構造体を関数から返します。

ファイル名:15_13_1.c

#include <stdio.h>

// テストの点数を表す構造体
typedef struct {
    int japanese;   // 国語
    int math;       // 数学
    int english;    // 英語
} Score;

// 集計結果を表す構造体
typedef struct {
    int total;      // 合計点
    double average; // 平均点
} Result;

// 点数から集計結果を作る関数
Result make_result(Score s);

int main(void)
{
    Score score = {78, 85, 91};

    Result result = make_result(score);

    printf("合計点: %d点\n", result.total);
    printf("平均点: %.1f点\n", result.average);

    return 0;
}

// 点数を集計して結果を返す関数
Result make_result(Score s)
{
    Result r;

    r.total = s.japanese + s.math + s.english;
    r.average = r.total / 3.0;

    return r;
}

実行結果例

合計点: 254点
平均点: 84.7点

このプログラムの流れ

このプログラムでは、まず main 関数の中で Score 型の変数 score を用意しています。

Score score = {78, 85, 91};

この score を make_result 関数に渡しています。

Result result = make_result(score);

この呼び出しによって、関数は Score 型のデータを受け取り、その内容をもとに Result 型の構造体を作ります。

関数の中では、次のようにローカル変数 r を作っています。

Result r;

そして、その r に合計点と平均点を代入しています。

r.total = s.japanese + s.math + s.english;
r.average = r.total / 3.0;

最後に、その r を戻り値として返します。

return r;

すると、返された構造体の内容が main 関数側の result に渡されます。

関数の中のローカル変数を返しても大丈夫なのか

ここはとても大事なところです。

関数の中で宣言したローカル変数は、関数が終わると使えなくなります。
そのため、初学者の方は「r は関数が終わったら消えるのに、return r; して大丈夫なのですか」と疑問に感じやすいです。

結論から言うと、構造体そのものを戻り値として返すのは問題ありません。

理由は、return r; としたときに、r という構造体の内容が呼び出し元へコピーされるからです。
つまり、main 関数側では、関数内の r そのものを使っているのではなく、その内容を受け取った別の構造体を使っています。

この点を整理すると、次のようになります。

項目内容
関数内の rローカル変数なので関数終了で消える
return rr の内容が戻り値として渡される
main 側の result返された内容を受け取った別の変数

この仕組みを理解しておくと、構造体を戻り値に使うことへの不安がかなり減ります。

この図では、関数内の Result r と、main 関数側の Result result を別々の箱として描くのがポイントです。

見た目の内容は同じでも、同じ変数ではありません。
関数内で作った結果が、戻り値として main 側へ渡され、main 側ではそれを result として受け取って使っています。

この「関数の中で作る」「戻り値として返す」「呼び出し元で受け取る」という流れが見えると、構造体を返す仕組みがとても理解しやすくなります。

なぜ構造体を返すと便利なのか

構造体を返す便利さは、複数の値をまとめて返せることにあります。

たとえば、先ほどの例で合計点だけを返すなら int で十分です。
でも、合計点と平均点の両方を返したいとき、int だけでは足りません。

もちろん、引数としてポインタを渡して、その先に結果を書き込む方法もあります。
ただ、構造体を戻り値として返すと、関数の役割が自然に表現できて、コードもわかりやすくなることが多いです。

次の表を見ると違いがつかみやすいです。

方法特徴
個別の値を返す1個の結果ならシンプル
ポインタ引数で結果を書き込む複数の結果を返せるが少し複雑
構造体を返す複数の結果を自然にまとめて返せる

関数の返り値として意味のあるまとまりをそのまま返せる、というのが構造体戻り値の魅力です。

戻り値の型に構造体名を書く

構造体を返す関数では、関数の戻り値の型として構造体型を書きます。

今回の例なら、次の部分です。

Result make_result(Score s);

これは、「make_result は Score 型の引数を受け取り、Result 型の値を返す関数です」という意味です。

普通の int を返す関数と比べると、次のような違いになります。

関数宣言意味
int calc(void);int 型を返す
Result make_result(Score s);Result 型の構造体を返す

ここで大切なのは、戻り値の型に使う構造体型は、あらかじめ定義されていなければならないということです。
そのため、構造体の typedef は関数宣言より前に書いておく必要があります。

値渡しとのつながりも意識しておく

このテーマは、前に学んだ構造体の値渡しともつながっています。

今回の関数 make_result は、引数として Score s を受け取っています。
これも値渡しなので、関数の中では s は呼び出し元の score のコピーです。

さらに、戻り値として返している Result r も、呼び出し元に内容が渡されます。
つまり、この関数では

  • 引数として構造体を受け取る
  • 関数内で別の構造体を作る
  • その構造体を戻り値として返す

という流れになっています。

構造体は「渡す」ことも「返す」こともできる、という点がとても大切です。

構造体を返すときのメリット

構造体を関数から返す方法には、いくつかの良い点があります。

メリット内容
複数の値をまとめて返せる関連するデータを1つにできる
関数の役割がわかりやすい何を返す関数なのか明確になる
コードが整理しやすい引数や戻り値の意味が見えやすい
安全に受け取れる戻り値としてコピーされるので扱いやすい

特に、計算結果や変換結果をまとめて返したいときにはとても便利です。

注意しておきたい点

便利な一方で、少し意識しておきたい点もあります。

まず、構造体が非常に大きい場合は、戻り値として返すときにコピーの負担が気になることがあります。
小さめの構造体ならあまり問題になりませんが、大きなデータを頻繁に返す設計では、効率を考える必要があることもあります。

また、関数の中で作ったローカル変数のアドレスを返すのは危険です。
これは今回の「構造体を返す」のとは別の話ですが、混同しやすいので注意したいところです。

たとえば、次のような書き方は危険です。

Result *make_result_bad(Score s)
{
    Result r;

    r.total = s.japanese + s.math + s.english;
    r.average = r.total / 3.0;

    return &r;
}

この場合、返しているのはローカル変数 r のアドレスです。
r は関数終了とともに消えるので、そのアドレスを呼び出し元で使うのは危険です。

今回安全なのは、アドレスではなく構造体そのものを返しているからです。

間違いやすいポイント

はじめのうちは、次の点を混同しやすいです。

間違いやすい点正しい考え方
ローカル変数は返せないのでは構造体そのものを返すなら内容が渡るので大丈夫
return したら同じ変数がそのまま移動する実際には呼び出し元で受け取る形になる
構造体は引数にしか使えない戻り値にも使える

このあたりを整理しておくと、関数と構造体の関係がかなりスッキリ見えてきます。

もう少し実用的な見方

実際のプログラムでは、構造体を返す関数は「何かを作る関数」や「変換する関数」でよく使われます。

たとえば、

  • 初期値入りの設定データを作る
  • 入力データから計算結果を作る
  • ある形式のデータを別形式へ変換する

といった処理です。

今回の例でいえば、Score から Result を作る関数は、まさに「元データから別の意味あるデータを作る関数」になっています。
このように考えると、構造体を返す関数はとても自然な書き方だと感じられるはずです。

覚えておきたいポイント

関数から構造体を返すときは、関数の戻り値の型に構造体型を書きます。
関数の中でローカル変数として構造体を作り、その内容を return で返せます。
そのとき、呼び出し元では返された構造体の内容を自分の変数として受け取って使います。

今回の内容で特に大切なのは、次の3点です。

ポイント内容
構造体は戻り値にできる複数の情報をまとめて返せる
return で安全に返せる内容が呼び出し元へ渡される
ローカル変数のアドレス返却とは別構造体そのものを返すのは安全

この考え方をしっかり押さえておくと、C言語で関数を設計するときの表現の幅がぐっと広がります。
構造体を受け取るだけでなく、構造体を返すことも自然に使えるようになると、プログラム全体がかなり整理しやすくなります。