C言語基礎|ポインタを返す関数

返り値がポインタだと、コードがつながる!―コピーして、そのまま次へ渡すCの書き味を体感しよう。

C言語の関数って「数値を返すもの」という印象が強いかもしれませんが、実はポインタ(アドレス)を返す関数もすごくよく出てきます。
ポインタを返せると何が嬉しいかというと、処理結果(たとえばコピー先の先頭)をそのまま次の関数に渡して、1行でスッとつなげられるんです。

前回の文字列コピー str_copy もまさにそれで、コピー先の先頭を返してくれるから、

  • コピーした上で表示する
  • 連続でコピーする(処理を鎖みたいにつなぐ)

みたいな書き方ができます。Cらしい“キレの良さ”が出るところですね。

今回扱うポイント(全体像)

この記事で登場する考え方

項目何をする話?何が得なの?
ポインタを返す関数の返り値がアドレスになる結果の場所を次に渡せる
先頭ポインタの保存途中でポインタを進めるので先頭を退避する返すべき値を失わない
連続呼び出し返り値を次の引数にそのまま渡す1行で処理をつなげられる

表の説明

  • ポインタを返す=「結果そのもの」ではなく「結果がある場所」を返す、という感覚です。
  • 文字列処理は“先頭を返す”設計が多く、応用が効きます。

サンプルプログラム

「コピーした文字列をそのまま表示」「連続コピー」を両方試せるプログラム例です。

プロジェクト名:chap11-6-1 ソースファイル名:chap11-6-1.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

#include <stdio.h>

// 文字列srcをdstにコピーし、dstの先頭を返す
char *str_copy(char *dst, const char *src)
{
    char *top = dst;

    while (*dst++ = *src++) {
        // '\0' までコピーしたら終了
    }
    return top;
}

int main(void)
{
    char input[128];
    char box1[128];
    char box2[128];

    printf("合言葉を入力してください:");
    scanf("%127s", input);

    // コピーして、その返り値(コピー先の先頭)をそのまま表示に渡す
    printf("箱1に入れた内容:%s\n", str_copy(box1, input));

    // 連続コピー:まず box2 にコピー → その結果(box2先頭)を使って box1 にもコピー
    str_copy(box1, str_copy(box2, input));

    printf("箱1:%s\n", box1);
    printf("箱2:%s\n", box2);

    return 0;
}

ポインタを返すってどういう意味?

よくある返り値との違い

返り値の種類返すものイメージ
int を返す数そのもの長さ 5答えを返す
char * を返す文字列の先頭アドレスコピー先の先頭答えがある場所を返す

表の説明

  • char * を返すと、「このアドレスから文字列がありますよ」と渡せます。
  • printf の %s は「渡されたポインタ(先頭アドレス)から '\0' まで」を表示するので、相性が最高です。

なぜ先頭を保存して返す必要があるの?

今回の str_copy は、コピーしながら dst をどんどん進めます。
つまり、最後の方では dst はもう先頭を指していません。

dst が進むので、先頭 top を別で持つ

開始直後
dst -> [ ? ][ ? ][ ? ]...
top -> [ ? ][ ? ][ ? ]...

コピーが進む
dst ->           [ ? ][ ? ]...
top -> [ ? ][ ? ][ ? ]...

図の説明

  • dst は走査用として移動する
  • top は「返したい先頭」を固定して持つ
    この二役がポイントです。

関数の書式と、登場する命令の役割

str_copy の書式

  • 書式:char *str_copy(char *dst, const char *src);

引数・返り値の役割

要素役割
dstchar *コピー先の先頭を指す。中身を書き換える
srcconst char *コピー元の先頭を指す。読むだけ
returnchar *コピー先の先頭を返す(dstの先頭)

表の説明

  • const char * は「src 側を変更しないよ」の合図です。
  • 返すのは「コピー結果が入った場所の先頭」です。

printf の書式と役割

  • 書式:printf(書式文字列, 引数...);
  • 何をする命令?:画面に整形して表示する命令
  • %s の意味:受け取ったポインタが指す先を「文字列として」表示する('\0' まで)

scanf の書式と役割

  • 書式:scanf(書式文字列, 格納先...);
  • 何をする命令?:入力を読み取り、指定場所へ書き込む命令
  • 今回の %127s:最大127文字まで読み、最後の '\0' 分の余裕を確保する

返り値を利用した「コピーしてそのまま表示」

この形が気持ちいいポイントです。

printf("箱1に入れた内容:%s\n", str_copy(box1, input));

この1行で起きていること

順番起きること
1str_copy が input を box1 にコピーする
2str_copy が box1 の先頭ポインタを返す
3printf がそのポインタを受け取り、box1 を文字列として表示する

表の説明

  • 「コピー」と「表示」が自然につながっています。
  • printf に渡しているのは box1 そのものではなく、box1 の先頭アドレス(box1[0]の位置)です。

返り値を利用した「連続コピー」

次は、返り値をさらに次の引数へ渡すパターンです。

str_copy(box1, str_copy(box2, input));

連続コピーの流れ(内側→外側)

1) 内側:str_copy(box2, input)
   input を box2 にコピー
   返り値:box2 の先頭

2) 外側:str_copy(box1, (返り値))
   box2 の内容を box1 にコピー

説明

  • C では関数呼び出しの入れ子は「内側が先に評価」されます。
  • だから「まず box2 を完成させて、その結果を box1 に流し込む」という順序になります。

連続コピーが便利な場面

何が嬉しい?
加工しながら繋ぐ変換関数の結果を次の関数へ渡せる。
ログ出力を短くする関数の結果をそのまま表示へ渡せる。
一時領域を使う中間結果を経由して最終結果に入れられる。

重要な注意(やってはいけない返し方)

「ポインタを返す関数」で一番事故りやすいのはこれです。

危険な返り値と安全な返り値

返すポインタ安全?理由
呼び出し側が用意した配列の先頭安全呼び出し後も生きている。
文字列リテラルの先頭だいたい安全(書き込みは注意)静的記憶域にあることが多い。
関数内のローカル変数のアドレス危険関数終了で寿命が切れる。

表の説明

  • 関数内のローカル配列 char tmp[128]; の先頭を return するのはダメです。
    関数が終わった瞬間にその領域は無効になります。